From e1b4d7f3753bf3a2cf2c235c41105e7a27043c12 Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Mon, 6 Mar 2017 14:32:16 -0500 Subject: [PATCH 1/9] Add support for switching to recommended perspective --- .../plugin.xml | 2 ++ .../newproject/AppEngineProjectWizard.java | 13 +++++++++++++ .../integration/appengine/BaseProjectTest.java | 3 +++ .../eclipse/util/service/ServiceContextFactory.java | 13 ++++++++++--- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.newproject/plugin.xml b/plugins/com.google.cloud.tools.eclipse.appengine.newproject/plugin.xml index 0f984f7f9a..1fe768a27b 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.newproject/plugin.xml +++ b/plugins/com.google.cloud.tools.eclipse.appengine.newproject/plugin.xml @@ -9,6 +9,8 @@ name="%standard.wizard.name" class="com.google.cloud.tools.eclipse.util.service.ServiceContextFactory:com.google.cloud.tools.eclipse.appengine.newproject.standard.AppEngineStandardProjectWizard" icon="platform:/plugin/com.google.cloud.tools.eclipse.appengine.ui/icons/gae-16x16.png" + finalPerspective="org.eclipse.jst.j2ee.J2EEPerspective" + preferredPerspectives="org.eclipse.jst.j2ee.J2EEPerspective,org.eclipse.wst.web.ui.webDevPerspective" project="true" category="com.google.cloud.tools.eclipse.wizards"> %standard.wizard.description diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.newproject/src/com/google/cloud/tools/eclipse/appengine/newproject/AppEngineProjectWizard.java b/plugins/com.google.cloud.tools.eclipse.appengine.newproject/src/com/google/cloud/tools/eclipse/appengine/newproject/AppEngineProjectWizard.java index a6c72420af..fed9be0f85 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.newproject/src/com/google/cloud/tools/eclipse/appengine/newproject/AppEngineProjectWizard.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.newproject/src/com/google/cloud/tools/eclipse/appengine/newproject/AppEngineProjectWizard.java @@ -25,25 +25,34 @@ import com.google.cloud.tools.eclipse.appengine.ui.CloudSdkOutOfDatePage; import com.google.cloud.tools.eclipse.sdk.ui.preferences.CloudSdkPrompter; import com.google.cloud.tools.eclipse.ui.util.WorkbenchUtil; +import com.google.cloud.tools.eclipse.usagetracker.AnalyticsEvents; +import com.google.cloud.tools.eclipse.util.service.ServiceContextFactory; import com.google.cloud.tools.eclipse.util.status.StatusUtil; import com.google.common.base.Preconditions; import java.io.File; import java.lang.reflect.InvocationTargetException; +import javax.inject.Inject; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.Wizard; import org.eclipse.ui.INewWizard; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.ide.undo.WorkspaceUndoUtil; +import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard; +/** 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 +106,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..415b284b7e 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 @@ -46,6 +46,9 @@ public static void setUp() throws Exception { } catch (WidgetNotFoundException ex) { // may receive WNFE: "There is no active view" } + + // switch to J2EE to avoid new-project switch-perspective prompts + bot.perspectiveById("org.eclipse.jst.j2ee.J2EEPerspective").activate(); } @After 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..fd12653c70 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,11 +29,10 @@ 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) @@ -40,6 +40,7 @@ public void setInitializationData(IConfigurationElement config, String propertyN 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); @@ -60,6 +61,12 @@ public void setInitializationData(IConfigurationElement config, String propertyN 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(); + } } } From 5cb26c4292cff5d3a17993cd246987af090ec87e Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Tue, 7 Mar 2017 14:44:38 -0500 Subject: [PATCH 2/9] Address review comments --- .../newproject/AppEngineProjectWizard.java | 6 +++- .../util/service/ServiceContextFactory.java | 32 +++++++++---------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.newproject/src/com/google/cloud/tools/eclipse/appengine/newproject/AppEngineProjectWizard.java b/plugins/com.google.cloud.tools.eclipse.appengine.newproject/src/com/google/cloud/tools/eclipse/appengine/newproject/AppEngineProjectWizard.java index fed9be0f85..722ada0f7a 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.newproject/src/com/google/cloud/tools/eclipse/appengine/newproject/AppEngineProjectWizard.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.newproject/src/com/google/cloud/tools/eclipse/appengine/newproject/AppEngineProjectWizard.java @@ -43,7 +43,11 @@ import org.eclipse.ui.ide.undo.WorkspaceUndoUtil; import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard; -/** Expected to be created via the {@link ServiceContextFactory}. */ +/** + * Base class for App Engine projection creation. + *

+ * Expected to be created via the {@link ServiceContextFactory}. + */ public abstract class AppEngineProjectWizard extends Wizard implements INewWizard { protected AppEngineWizardPage page = null; 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 fd12653c70..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 @@ -37,24 +37,22 @@ public class ServiceContextFactory implements IExecutableExtensionFactory, IExec @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")); - } + 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)); - } + 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 From 00a86f179fdf0a37c6955a85198a09b847e6248d Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Tue, 7 Mar 2017 16:30:46 -0500 Subject: [PATCH 3/9] Ensure integration tests reset to the J2EE perspective. --- .../eclipse/integration/appengine/BaseProjectTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 415b284b7e..aadb0136fd 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(); @@ -46,9 +47,13 @@ public static void setUp() throws Exception { } catch (WidgetNotFoundException ex) { // may receive WNFE: "There is no active view" } + } + @Before + public void setUp() { // switch to J2EE to avoid new-project switch-perspective prompts bot.perspectiveById("org.eclipse.jst.j2ee.J2EEPerspective").activate(); + } @After From 1fc1faddd6174283d5ca0d95d5493df82d701f3e Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Thu, 13 Jul 2017 11:51:48 -0400 Subject: [PATCH 4/9] Copy devappserver console for debugging purposes --- .../appengine/DebugNativeAppEngineStandardProjectTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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!", From 4e3f72dd47f3ff1608442c933961166586856f87 Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Wed, 23 Aug 2017 11:29:45 -0400 Subject: [PATCH 5/9] Ensure test projects have different names to avoid class; log if project deletion timesout --- .../eclipse/integration/appengine/BaseProjectTest.java | 1 + .../appengine/NewMavenBasedAppEngineProjectWizardTest.java | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) 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 aadb0136fd..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 @@ -68,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/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); } From 6b4d7b22428290d98e2d9f377cce264c21b840a8 Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Fri, 11 Aug 2017 12:10:20 -0400 Subject: [PATCH 6/9] Include patched org.eclipse.core.resources for Eclipse Bug 519776 Bring in patched versions of org.eclipse.core.resources to check if https://git.eclipse.org/c/101379 fixes bug 519776 and fixes our test errors. --- .mvn/extensions.xml | 10 + pom.xml | 3 + third_party/patches/README.md | 2 + .../org.eclipse.core.resources/.classpath | 7 + .../mars/org.eclipse.core.resources/.project | 17 + .../META-INF/MANIFEST.MF | 33 + .../OSGI-INF/l10n/bundle-src.properties | 4 + .../org.eclipse.core.resources/about.html | 28 + .../resources-antsrc/META-INF/eclipse.inf | 2 + .../core/resources/ant/ConvertPath.java | 176 ++ .../core/resources/ant/IncrementalBuild.java | 126 + .../eclipse/core/resources/ant/Policy.java | 71 + .../core/resources/ant/RefreshLocalTask.java | 120 + .../core/resources/ant/messages.properties | 22 + .../build.properties | 22 + .../natives/make.bat | 21 + .../natives/readme.txt | 3 + .../org.eclipse.core.resources/natives/ref.c | 297 ++ .../org.eclipse.core.resources/natives/ref.h | 213 ++ .../plugin.properties | 40 + .../org.eclipse.core.resources/plugin.xml | 260 ++ .../schema/builders.exsd | 216 ++ .../schema/fileModificationValidator.exsd | 111 + .../schema/filterMatchers.exsd | 187 ++ .../schema/markers.exsd | 167 + .../schema/modelProviders.exsd | 159 + .../schema/moveDeleteHook.exsd | 120 + .../schema/natures.exsd | 334 ++ .../schema/refreshProviders.exsd | 133 + .../schema/teamHook.exsd | 120 + .../schema/variableResolvers.exsd | 139 + .../core/internal/dtree/AbstractDataTree.java | 330 ++ .../internal/dtree/AbstractDataTreeNode.java | 598 ++++ .../core/internal/dtree/DataDeltaNode.java | 121 + .../eclipse/core/internal/dtree/DataTree.java | 294 ++ .../core/internal/dtree/DataTreeLookup.java | 80 + .../core/internal/dtree/DataTreeNode.java | 369 +++ .../core/internal/dtree/DataTreeReader.java | 150 + .../core/internal/dtree/DataTreeWriter.java | 184 ++ .../core/internal/dtree/DeletedNode.java | 131 + .../core/internal/dtree/DeltaDataTree.java | 977 ++++++ .../core/internal/dtree/IComparator.java | 28 + .../core/internal/dtree/IDataFlattener.java | 39 + .../core/internal/dtree/NoDataDeltaNode.java | 133 + .../core/internal/dtree/NodeComparison.java | 136 + .../dtree/ObjectNotFoundException.java | 30 + .../core/internal/dtree/TestHelper.java | 23 + .../core/internal/events/AutoBuildJob.java | 276 ++ .../core/internal/events/BuildCommand.java | 314 ++ .../core/internal/events/BuildContext.java | 128 + .../core/internal/events/BuildManager.java | 1157 +++++++ .../events/BuilderPersistentInfo.java | 81 + .../internal/events/ILifecycleListener.java | 21 + .../core/internal/events/InternalBuilder.java | 248 ++ .../core/internal/events/LifecycleEvent.java | 91 + .../core/internal/events/NodeIDMap.java | 221 ++ .../internal/events/NotificationManager.java | 341 ++ .../events/PathVariableChangeEvent.java | 93 + .../internal/events/ResourceChangeEvent.java | 155 + .../events/ResourceChangeListenerList.java | 180 ++ .../internal/events/ResourceComparator.java | 200 ++ .../core/internal/events/ResourceDelta.java | 550 ++++ .../internal/events/ResourceDeltaFactory.java | 184 ++ .../internal/events/ResourceDeltaInfo.java | 58 + .../core/internal/events/ResourceStats.java | 96 + .../core/internal/localstore/BlobStore.java | 137 + .../core/internal/localstore/Bucket.java | 395 +++ .../core/internal/localstore/BucketTree.java | 160 + .../localstore/CollectSyncStatusVisitor.java | 116 + .../core/internal/localstore/CopyVisitor.java | 211 ++ .../internal/localstore/DeleteVisitor.java | 154 + .../internal/localstore/FileStoreRoot.java | 199 ++ .../localstore/FileSystemResourceManager.java | 1213 +++++++ .../internal/localstore/HistoryBucket.java | 326 ++ .../internal/localstore/HistoryStore2.java | 389 +++ .../internal/localstore/IHistoryStore.java | 178 ++ .../localstore/ILocalStoreConstants.java | 31 + .../localstore/IUnifiedTreeVisitor.java | 20 + .../localstore/IsSynchronizedVisitor.java | 59 + .../core/internal/localstore/PrefixPool.java | 207 ++ .../localstore/RefreshLocalAliasVisitor.java | 110 + .../localstore/RefreshLocalVisitor.java | 334 ++ .../localstore/SafeChunkyInputStream.java | 184 ++ .../localstore/SafeChunkyOutputStream.java | 81 + .../localstore/SafeFileInputStream.java | 53 + .../localstore/SafeFileOutputStream.java | 141 + .../core/internal/localstore/UnifiedTree.java | 573 ++++ .../internal/localstore/UnifiedTreeNode.java | 149 + .../internal/properties/IPropertyManager.java | 75 + .../internal/properties/PropertyBucket.java | 355 +++ .../internal/properties/PropertyManager2.java | 190 ++ .../propertytester/FilePropertyTester.java | 123 + .../propertytester/ProjectPropertyTester.java | 40 + .../ResourceMappingPropertyTester.java | 70 + .../ResourcePropertyTester.java | 221 ++ .../propertytester/StringMatcher.java | 230 ++ .../refresh/InternalRefreshProvider.java | 42 + .../core/internal/refresh/MonitorManager.java | 355 +++ .../core/internal/refresh/PollingMonitor.java | 220 ++ .../core/internal/refresh/RefreshJob.java | 229 ++ .../core/internal/refresh/RefreshManager.java | 117 + .../core/internal/resources/AliasManager.java | 767 +++++ .../resources/BuildConfiguration.java | 138 + .../internal/resources/CharsetDeltaJob.java | 214 ++ .../internal/resources/CharsetManager.java | 507 +++ .../resources/ComputeProjectOrder.java | 620 ++++ .../core/internal/resources/Container.java | 386 +++ .../resources/ContentDescriptionManager.java | 547 ++++ .../resources/DelayedSnapshotJob.java | 60 + .../eclipse/core/internal/resources/File.java | 436 +++ .../core/internal/resources/FileState.java | 129 + .../core/internal/resources/Filter.java | 177 ++ .../internal/resources/FilterDescription.java | 143 + .../internal/resources/FilterDescriptor.java | 95 + .../internal/resources/FilterTypeManager.java | 105 + .../core/internal/resources/Folder.java | 186 ++ .../internal/resources/ICoreConstants.java | 125 + .../core/internal/resources/IManager.java | 20 + .../internal/resources/IMarkerSetElement.java | 15 + .../resources/IModelObjectConstants.java | 58 + .../internal/resources/InternalTeamHook.java | 32 + .../resources/InternalWorkspaceJob.java | 54 + .../internal/resources/LinkDescription.java | 126 + .../internal/resources/LocalMetaArea.java | 512 +++ .../internal/resources/LocationValidator.java | 438 +++ .../core/internal/resources/Marker.java | 339 ++ .../resources/MarkerAttributeMap.java | 333 ++ .../core/internal/resources/MarkerDelta.java | 246 ++ .../resources/MarkerDeltaManager.java | 91 + .../core/internal/resources/MarkerInfo.java | 209 ++ .../internal/resources/MarkerManager.java | 674 ++++ .../core/internal/resources/MarkerReader.java | 56 + .../internal/resources/MarkerReader_1.java | 144 + .../internal/resources/MarkerReader_2.java | 145 + .../internal/resources/MarkerReader_3.java | 161 + .../core/internal/resources/MarkerSet.java | 247 ++ .../resources/MarkerSnapshotReader.java | 50 + .../resources/MarkerSnapshotReader_1.java | 128 + .../resources/MarkerSnapshotReader_2.java | 143 + .../resources/MarkerTypeDefinitionCache.java | 141 + .../core/internal/resources/MarkerWriter.java | 216 ++ .../core/internal/resources/ModelObject.java | 40 + .../internal/resources/ModelObjectWriter.java | 304 ++ .../internal/resources/MoveDeleteHook.java | 77 + .../internal/resources/NatureManager.java | 642 ++++ .../eclipse/core/internal/resources/OS.java | 78 + .../resources/PathVariableManager.java | 394 +++ .../internal/resources/PathVariableUtil.java | 506 +++ .../PlatformURLResourceConnection.java | 108 + .../resources/PreferenceInitializer.java | 81 + .../core/internal/resources/Project.java | 1371 ++++++++ .../resources/ProjectContentTypes.java | 250 ++ .../resources/ProjectDescription.java | 931 ++++++ .../resources/ProjectDescriptionReader.java | 1117 +++++++ .../core/internal/resources/ProjectInfo.java | 140 + .../resources/ProjectNatureDescriptor.java | 167 + .../resources/ProjectPathVariableManager.java | 507 +++ .../resources/ProjectPreferences.java | 691 ++++ .../ProjectVariableProviderManager.java | 118 + .../resources/RegexFileInfoMatcher.java | 51 + .../core/internal/resources/Resource.java | 2083 ++++++++++++ .../internal/resources/ResourceException.java | 98 + .../core/internal/resources/ResourceInfo.java | 476 +++ .../internal/resources/ResourceProxy.java | 134 + .../internal/resources/ResourceStatus.java | 85 + .../core/internal/resources/ResourceTree.java | 1165 +++++++ .../core/internal/resources/RootInfo.java | 53 + .../core/internal/resources/Rules.java | 232 ++ .../internal/resources/SafeFileTable.java | 99 + .../core/internal/resources/SaveContext.java | 134 + .../core/internal/resources/SaveManager.java | 2140 +++++++++++++ .../core/internal/resources/SavedState.java | 90 + .../internal/resources/SyncInfoReader.java | 79 + .../internal/resources/SyncInfoReader_2.java | 98 + .../internal/resources/SyncInfoReader_3.java | 98 + .../resources/SyncInfoSnapReader.java | 51 + .../resources/SyncInfoSnapReader_3.java | 64 + .../internal/resources/SyncInfoWriter.java | 121 + .../core/internal/resources/Synchronizer.java | 284 ++ .../internal/resources/TestingSupport.java | 49 + .../resources/VariableDescription.java | 73 + .../internal/resources/VirtualFileStore.java | 80 + .../internal/resources/VirtualFileSystem.java | 31 + .../core/internal/resources/WorkManager.java | 324 ++ .../core/internal/resources/Workspace.java | 2558 +++++++++++++++ .../resources/WorkspaceDescription.java | 201 ++ .../resources/WorkspaceDescriptionReader.java | 168 + .../resources/WorkspacePreferences.java | 256 ++ .../internal/resources/WorkspaceRoot.java | 295 ++ .../resources/WorkspaceTreeReader.java | 83 + .../resources/WorkspaceTreeReader_1.java | 270 ++ .../resources/WorkspaceTreeReader_2.java | 218 ++ .../core/internal/resources/XMLWriter.java | 129 + .../resources/mapping/ChangeDescription.java | 134 + .../mapping/ModelProviderDescriptor.java | 145 + .../mapping/ModelProviderManager.java | 77 + .../mapping/ProposedResourceDelta.java | 226 ++ .../mapping/ResourceAdapterFactory.java | 36 + .../ResourceChangeDescriptionFactory.java | 261 ++ .../mapping/ResourceModelProvider.java | 77 + .../resources/mapping/ShallowContainer.java | 56 + .../mapping/ShallowResourceMapping.java | 68 + .../mapping/SimpleResourceMapping.java | 83 + .../EclipseHomeProjectVariable.java | 47 + .../ParentVariableResolver.java | 64 + .../ProjectLocationVariableResolver.java | 39 + .../WorkspaceLocationVariableResolver.java | 36 + ...rkspaceParentLocationVariableResolver.java | 45 + .../resources/refresh/win32/Convert.java | 50 + .../resources/refresh/win32/Win32Monitor.java | 607 ++++ .../resources/refresh/win32/Win32Natives.java | 347 ++ .../refresh/win32/Win32RefreshProvider.java | 40 + .../core/internal/utils/ArrayIterator.java | 65 + .../eclipse/core/internal/utils/BitMask.java | 23 + .../eclipse/core/internal/utils/Cache.java | 207 ++ .../eclipse/core/internal/utils/Convert.java | 86 + .../eclipse/core/internal/utils/FileUtil.java | 449 +++ .../utils/IStringPoolParticipant.java | 32 + .../core/internal/utils/KeyedHashSet.java | 243 ++ .../eclipse/core/internal/utils/Messages.java | 328 ++ .../core/internal/utils/ObjectMap.java | 329 ++ .../eclipse/core/internal/utils/Policy.java | 190 ++ .../eclipse/core/internal/utils/Queue.java | 201 ++ .../core/internal/utils/StringPool.java | 75 + .../core/internal/utils/StringPoolJob.java | 140 + .../utils/UniversalUniqueIdentifier.java | 357 +++ .../utils/WrappedRuntimeException.java | 35 + .../core/internal/utils/messages.properties | 322 ++ .../watson/DefaultElementComparator.java | 64 + .../core/internal/watson/ElementTree.java | 731 +++++ .../internal/watson/ElementTreeIterator.java | 169 + .../internal/watson/ElementTreeReader.java | 155 + .../watson/ElementTreeReaderImpl_1.java | 109 + .../internal/watson/ElementTreeWriter.java | 240 ++ .../internal/watson/IElementComparator.java | 25 + .../watson/IElementContentVisitor.java | 30 + .../watson/IElementInfoFlattener.java | 39 + .../internal/watson/IElementTreeData.java | 22 + .../core/internal/watson/IPathRequestor.java | 23 + .../resources/FileInfoMatcherDescription.java | 73 + .../core/resources/IBuildConfiguration.java | 82 + .../eclipse/core/resources/IBuildContext.java | 63 + .../org/eclipse/core/resources/ICommand.java | 152 + .../eclipse/core/resources/IContainer.java | 562 ++++ .../core/resources/IEncodedStorage.java | 44 + .../src/org/eclipse/core/resources/IFile.java | 1146 +++++++ .../resources/IFileModificationValidator.java | 68 + .../eclipse/core/resources/IFileState.java | 121 + .../resources/IFilterMatcherDescriptor.java | 84 + .../org/eclipse/core/resources/IFolder.java | 461 +++ .../org/eclipse/core/resources/IMarker.java | 578 ++++ .../eclipse/core/resources/IMarkerDelta.java | 180 ++ .../resources/IPathVariableChangeEvent.java | 72 + .../IPathVariableChangeListener.java | 40 + .../core/resources/IPathVariableManager.java | 352 +++ .../org/eclipse/core/resources/IProject.java | 1055 +++++++ .../core/resources/IProjectDescription.java | 385 +++ .../core/resources/IProjectNature.java | 81 + .../resources/IProjectNatureDescriptor.java | 88 + .../org/eclipse/core/resources/IResource.java | 2805 +++++++++++++++++ .../core/resources/IResourceChangeEvent.java | 266 ++ .../resources/IResourceChangeListener.java | 47 + .../core/resources/IResourceDelta.java | 591 ++++ .../core/resources/IResourceDeltaVisitor.java | 55 + .../resources/IResourceFilterDescription.java | 119 + .../core/resources/IResourceProxy.java | 163 + .../core/resources/IResourceProxyVisitor.java | 52 + .../core/resources/IResourceRuleFactory.java | 161 + .../core/resources/IResourceStatus.java | 322 ++ .../core/resources/IResourceVisitor.java | 46 + .../eclipse/core/resources/ISaveContext.java | 197 ++ .../core/resources/ISaveParticipant.java | 172 + .../eclipse/core/resources/ISavedState.java | 87 + .../org/eclipse/core/resources/IStorage.java | 68 + .../eclipse/core/resources/ISynchronizer.java | 135 + .../eclipse/core/resources/IWorkspace.java | 1748 ++++++++++ .../core/resources/IWorkspaceDescription.java | 268 ++ .../core/resources/IWorkspaceRoot.java | 394 +++ .../core/resources/IWorkspaceRunnable.java | 42 + .../resources/IncrementalProjectBuilder.java | 472 +++ .../eclipse/core/resources/ProjectScope.java | 111 + .../core/resources/ResourceAttributes.java | 234 ++ .../core/resources/ResourcesPlugin.java | 484 +++ .../eclipse/core/resources/WorkspaceJob.java | 83 + .../AbstractFileInfoMatcher.java | 49 + .../CompoundFileInfoMatcher.java | 50 + .../resources/filtermatchers/package.html | 21 + .../mapping/CompositeResourceMapping.java | 118 + .../mapping/IModelProviderDescriptor.java | 98 + .../IResourceChangeDescriptionFactory.java | 84 + .../core/resources/mapping/ModelProvider.java | 268 ++ .../core/resources/mapping/ModelStatus.java | 56 + .../mapping/RemoteResourceMappingContext.java | 321 ++ .../mapping/ResourceChangeValidator.java | 168 + .../resources/mapping/ResourceMapping.java | 204 ++ .../mapping/ResourceMappingContext.java | 45 + .../resources/mapping/ResourceTraversal.java | 182 ++ .../core/resources/mapping/package.html | 27 + .../org/eclipse/core/resources/package.html | 25 + .../resources/refresh/IRefreshMonitor.java | 37 + .../resources/refresh/IRefreshResult.java | 49 + .../resources/refresh/RefreshProvider.java | 86 + .../core/resources/refresh/package.html | 23 + .../FileModificationValidationContext.java | 56 + .../team/FileModificationValidator.java | 102 + .../core/resources/team/IMoveDeleteHook.java | 383 +++ .../core/resources/team/IResourceTree.java | 383 +++ .../resources/team/ResourceRuleFactory.java | 273 ++ .../eclipse/core/resources/team/TeamHook.java | 231 ++ .../eclipse/core/resources/team/package.html | 19 + .../PathVariableResolver.java | 52 + .../resources/variableresolvers/package.html | 21 + third_party/patches/mars/pom.xml | 21 + .../org.eclipse.core.resources/.classpath | 7 + .../neon/org.eclipse.core.resources/.project | 17 + .../META-INF/MANIFEST.MF | 32 + .../OSGI-INF/l10n/bundle-src.properties | 4 + .../org.eclipse.core.resources/about.html | 28 + .../resources-antsrc/META-INF/eclipse.inf | 2 + .../core/resources/ant/ConvertPath.java | 176 ++ .../core/resources/ant/IncrementalBuild.java | 126 + .../eclipse/core/resources/ant/Policy.java | 71 + .../core/resources/ant/RefreshLocalTask.java | 120 + .../core/resources/ant/messages.properties | 22 + .../build.properties | 22 + .../natives/make.bat | 21 + .../natives/readme.txt | 3 + .../org.eclipse.core.resources/natives/ref.c | 297 ++ .../org.eclipse.core.resources/natives/ref.h | 213 ++ .../plugin.properties | 40 + .../org.eclipse.core.resources/plugin.xml | 260 ++ .../schema/builders.exsd | 216 ++ .../schema/fileModificationValidator.exsd | 111 + .../schema/filterMatchers.exsd | 187 ++ .../schema/markers.exsd | 167 + .../schema/modelProviders.exsd | 159 + .../schema/moveDeleteHook.exsd | 120 + .../schema/natures.exsd | 334 ++ .../schema/refreshProviders.exsd | 133 + .../schema/teamHook.exsd | 120 + .../schema/variableResolvers.exsd | 139 + .../core/internal/dtree/AbstractDataTree.java | 330 ++ .../internal/dtree/AbstractDataTreeNode.java | 598 ++++ .../core/internal/dtree/DataDeltaNode.java | 121 + .../eclipse/core/internal/dtree/DataTree.java | 294 ++ .../core/internal/dtree/DataTreeLookup.java | 80 + .../core/internal/dtree/DataTreeNode.java | 366 +++ .../core/internal/dtree/DataTreeReader.java | 150 + .../core/internal/dtree/DataTreeWriter.java | 184 ++ .../core/internal/dtree/DeletedNode.java | 131 + .../core/internal/dtree/DeltaDataTree.java | 977 ++++++ .../core/internal/dtree/IComparator.java | 28 + .../core/internal/dtree/IDataFlattener.java | 39 + .../core/internal/dtree/NoDataDeltaNode.java | 133 + .../core/internal/dtree/NodeComparison.java | 136 + .../dtree/ObjectNotFoundException.java | 30 + .../core/internal/dtree/TestHelper.java | 23 + .../core/internal/events/AutoBuildJob.java | 276 ++ .../core/internal/events/BuildCommand.java | 291 ++ .../core/internal/events/BuildContext.java | 120 + .../core/internal/events/BuildManager.java | 1158 +++++++ .../events/BuilderPersistentInfo.java | 81 + .../internal/events/ILifecycleListener.java | 21 + .../core/internal/events/InternalBuilder.java | 248 ++ .../core/internal/events/LifecycleEvent.java | 91 + .../core/internal/events/NodeIDMap.java | 221 ++ .../internal/events/NotificationManager.java | 341 ++ .../events/PathVariableChangeEvent.java | 93 + .../internal/events/ResourceChangeEvent.java | 156 + .../events/ResourceChangeListenerList.java | 180 ++ .../internal/events/ResourceComparator.java | 200 ++ .../core/internal/events/ResourceDelta.java | 552 ++++ .../internal/events/ResourceDeltaFactory.java | 184 ++ .../internal/events/ResourceDeltaInfo.java | 58 + .../core/internal/events/ResourceStats.java | 96 + .../core/internal/localstore/BlobStore.java | 138 + .../core/internal/localstore/Bucket.java | 396 +++ .../core/internal/localstore/BucketTree.java | 160 + .../localstore/CollectSyncStatusVisitor.java | 117 + .../core/internal/localstore/CopyVisitor.java | 211 ++ .../internal/localstore/DeleteVisitor.java | 154 + .../internal/localstore/FileStoreRoot.java | 201 ++ .../localstore/FileSystemResourceManager.java | 1235 ++++++++ .../internal/localstore/HistoryBucket.java | 326 ++ .../internal/localstore/HistoryStore2.java | 383 +++ .../internal/localstore/IHistoryStore.java | 178 ++ .../localstore/ILocalStoreConstants.java | 31 + .../localstore/IUnifiedTreeVisitor.java | 20 + .../localstore/IsSynchronizedVisitor.java | 59 + .../core/internal/localstore/PrefixPool.java | 207 ++ .../localstore/RefreshLocalAliasVisitor.java | 110 + .../localstore/RefreshLocalVisitor.java | 314 ++ .../localstore/SafeChunkyInputStream.java | 184 ++ .../localstore/SafeChunkyOutputStream.java | 81 + .../localstore/SafeFileInputStream.java | 53 + .../localstore/SafeFileOutputStream.java | 141 + .../core/internal/localstore/UnifiedTree.java | 575 ++++ .../internal/localstore/UnifiedTreeNode.java | 149 + .../internal/properties/IPropertyManager.java | 75 + .../internal/properties/PropertyBucket.java | 350 ++ .../internal/properties/PropertyManager2.java | 192 ++ .../propertytester/FilePropertyTester.java | 117 + .../propertytester/ProjectPropertyTester.java | 34 + .../ResourceMappingPropertyTester.java | 70 + .../ResourcePropertyTester.java | 221 ++ .../propertytester/StringMatcher.java | 231 ++ .../refresh/InternalRefreshProvider.java | 42 + .../core/internal/refresh/MonitorManager.java | 356 +++ .../core/internal/refresh/PollingMonitor.java | 218 ++ .../core/internal/refresh/RefreshJob.java | 217 ++ .../core/internal/refresh/RefreshManager.java | 117 + .../core/internal/resources/AliasManager.java | 762 +++++ .../resources/BuildConfiguration.java | 118 + .../internal/resources/CharsetDeltaJob.java | 212 ++ .../internal/resources/CharsetManager.java | 502 +++ .../resources/ComputeProjectOrder.java | 621 ++++ .../core/internal/resources/Container.java | 387 +++ .../resources/ContentDescriptionManager.java | 549 ++++ .../resources/DelayedSnapshotJob.java | 60 + .../eclipse/core/internal/resources/File.java | 444 +++ .../core/internal/resources/FileState.java | 108 + .../core/internal/resources/Filter.java | 177 ++ .../internal/resources/FilterDescription.java | 144 + .../internal/resources/FilterDescriptor.java | 77 + .../internal/resources/FilterTypeManager.java | 106 + .../core/internal/resources/Folder.java | 186 ++ .../internal/resources/ICoreConstants.java | 125 + .../core/internal/resources/IManager.java | 20 + .../internal/resources/IMarkerSetElement.java | 15 + .../resources/IModelObjectConstants.java | 58 + .../internal/resources/InternalTeamHook.java | 33 + .../resources/InternalWorkspaceJob.java | 54 + .../internal/resources/LinkDescription.java | 126 + .../internal/resources/LocalMetaArea.java | 514 +++ .../internal/resources/LocationValidator.java | 438 +++ .../core/internal/resources/Marker.java | 340 ++ .../resources/MarkerAttributeMap.java | 295 ++ .../core/internal/resources/MarkerDelta.java | 210 ++ .../resources/MarkerDeltaManager.java | 92 + .../core/internal/resources/MarkerInfo.java | 211 ++ .../internal/resources/MarkerManager.java | 669 ++++ .../core/internal/resources/MarkerReader.java | 57 + .../internal/resources/MarkerReader_1.java | 146 + .../internal/resources/MarkerReader_2.java | 147 + .../internal/resources/MarkerReader_3.java | 163 + .../core/internal/resources/MarkerSet.java | 247 ++ .../resources/MarkerSnapshotReader.java | 51 + .../resources/MarkerSnapshotReader_1.java | 130 + .../resources/MarkerSnapshotReader_2.java | 145 + .../resources/MarkerTypeDefinitionCache.java | 142 + .../core/internal/resources/MarkerWriter.java | 218 ++ .../core/internal/resources/ModelObject.java | 40 + .../internal/resources/ModelObjectWriter.java | 306 ++ .../internal/resources/MoveDeleteHook.java | 77 + .../internal/resources/NatureManager.java | 643 ++++ .../eclipse/core/internal/resources/OS.java | 78 + .../resources/PathVariableManager.java | 390 +++ .../internal/resources/PathVariableUtil.java | 504 +++ .../PlatformURLResourceConnection.java | 108 + .../resources/PreferenceInitializer.java | 78 + .../core/internal/resources/Project.java | 1370 ++++++++ .../resources/ProjectContentTypes.java | 245 ++ .../resources/ProjectDescription.java | 877 ++++++ .../resources/ProjectDescriptionReader.java | 1119 +++++++ .../core/internal/resources/ProjectInfo.java | 141 + .../resources/ProjectNatureDescriptor.java | 168 + .../resources/ProjectPathVariableManager.java | 503 +++ .../resources/ProjectPreferences.java | 689 ++++ .../ProjectVariableProviderManager.java | 119 + .../resources/RegexFileInfoMatcher.java | 51 + .../core/internal/resources/Resource.java | 2084 ++++++++++++ .../internal/resources/ResourceException.java | 98 + .../core/internal/resources/ResourceInfo.java | 477 +++ .../internal/resources/ResourceProxy.java | 134 + .../internal/resources/ResourceStatus.java | 85 + .../core/internal/resources/ResourceTree.java | 1165 +++++++ .../core/internal/resources/RootInfo.java | 53 + .../core/internal/resources/Rules.java | 230 ++ .../internal/resources/SafeFileTable.java | 99 + .../core/internal/resources/SaveContext.java | 134 + .../core/internal/resources/SaveManager.java | 2139 +++++++++++++ .../core/internal/resources/SavedState.java | 90 + .../internal/resources/SyncInfoReader.java | 81 + .../internal/resources/SyncInfoReader_2.java | 99 + .../internal/resources/SyncInfoReader_3.java | 99 + .../resources/SyncInfoSnapReader.java | 52 + .../resources/SyncInfoSnapReader_3.java | 65 + .../internal/resources/SyncInfoWriter.java | 121 + .../core/internal/resources/Synchronizer.java | 285 ++ .../internal/resources/TestingSupport.java | 49 + .../resources/VariableDescription.java | 73 + .../internal/resources/VirtualFileStore.java | 80 + .../internal/resources/VirtualFileSystem.java | 31 + .../core/internal/resources/WorkManager.java | 325 ++ .../core/internal/resources/Workspace.java | 2571 +++++++++++++++ .../resources/WorkspaceDescription.java | 201 ++ .../resources/WorkspaceDescriptionReader.java | 169 + .../resources/WorkspacePreferences.java | 257 ++ .../internal/resources/WorkspaceRoot.java | 295 ++ .../resources/WorkspaceTreeReader.java | 84 + .../resources/WorkspaceTreeReader_1.java | 271 ++ .../resources/WorkspaceTreeReader_2.java | 219 ++ .../core/internal/resources/XMLWriter.java | 129 + .../resources/mapping/ChangeDescription.java | 135 + .../mapping/ModelProviderDescriptor.java | 146 + .../mapping/ModelProviderManager.java | 78 + .../mapping/ProposedResourceDelta.java | 182 ++ .../mapping/ResourceAdapterFactory.java | 36 + .../ResourceChangeDescriptionFactory.java | 240 ++ .../mapping/ResourceModelProvider.java | 73 + .../resources/mapping/ShallowContainer.java | 56 + .../mapping/ShallowResourceMapping.java | 68 + .../mapping/SimpleResourceMapping.java | 71 + .../EclipseHomeProjectVariable.java | 47 + .../ParentVariableResolver.java | 64 + .../ProjectLocationVariableResolver.java | 39 + .../WorkspaceLocationVariableResolver.java | 36 + ...rkspaceParentLocationVariableResolver.java | 45 + .../resources/refresh/win32/Convert.java | 50 + .../resources/refresh/win32/Win32Monitor.java | 603 ++++ .../resources/refresh/win32/Win32Natives.java | 347 ++ .../refresh/win32/Win32RefreshProvider.java | 40 + .../core/internal/utils/ArrayIterator.java | 65 + .../eclipse/core/internal/utils/BitMask.java | 23 + .../eclipse/core/internal/utils/Cache.java | 207 ++ .../eclipse/core/internal/utils/Convert.java | 86 + .../eclipse/core/internal/utils/FileUtil.java | 436 +++ .../utils/IStringPoolParticipant.java | 32 + .../core/internal/utils/KeyedHashSet.java | 243 ++ .../eclipse/core/internal/utils/Messages.java | 328 ++ .../core/internal/utils/ObjectMap.java | 330 ++ .../eclipse/core/internal/utils/Policy.java | 181 ++ .../eclipse/core/internal/utils/Queue.java | 201 ++ .../core/internal/utils/StringPool.java | 76 + .../core/internal/utils/StringPoolJob.java | 137 + .../utils/UniversalUniqueIdentifier.java | 359 +++ .../utils/WrappedRuntimeException.java | 35 + .../core/internal/utils/messages.properties | 322 ++ .../watson/DefaultElementComparator.java | 64 + .../core/internal/watson/ElementTree.java | 732 +++++ .../internal/watson/ElementTreeIterator.java | 169 + .../internal/watson/ElementTreeReader.java | 155 + .../watson/ElementTreeReaderImpl_1.java | 101 + .../internal/watson/ElementTreeWriter.java | 242 ++ .../internal/watson/IElementComparator.java | 25 + .../watson/IElementContentVisitor.java | 30 + .../watson/IElementInfoFlattener.java | 39 + .../internal/watson/IElementTreeData.java | 22 + .../core/internal/watson/IPathRequestor.java | 23 + .../resources/FileInfoMatcherDescription.java | 67 + .../core/resources/IBuildConfiguration.java | 82 + .../eclipse/core/resources/IBuildContext.java | 63 + .../org/eclipse/core/resources/ICommand.java | 152 + .../eclipse/core/resources/IContainer.java | 562 ++++ .../core/resources/IEncodedStorage.java | 44 + .../src/org/eclipse/core/resources/IFile.java | 1162 +++++++ .../resources/IFileModificationValidator.java | 68 + .../eclipse/core/resources/IFileState.java | 121 + .../resources/IFilterMatcherDescriptor.java | 84 + .../org/eclipse/core/resources/IFolder.java | 461 +++ .../org/eclipse/core/resources/IMarker.java | 578 ++++ .../eclipse/core/resources/IMarkerDelta.java | 180 ++ .../resources/IPathVariableChangeEvent.java | 72 + .../IPathVariableChangeListener.java | 40 + .../core/resources/IPathVariableManager.java | 352 +++ .../org/eclipse/core/resources/IProject.java | 1055 +++++++ .../core/resources/IProjectDescription.java | 385 +++ .../core/resources/IProjectNature.java | 81 + .../resources/IProjectNatureDescriptor.java | 88 + .../org/eclipse/core/resources/IResource.java | 2805 +++++++++++++++++ .../core/resources/IResourceChangeEvent.java | 266 ++ .../resources/IResourceChangeListener.java | 47 + .../core/resources/IResourceDelta.java | 591 ++++ .../core/resources/IResourceDeltaVisitor.java | 55 + .../resources/IResourceFilterDescription.java | 119 + .../core/resources/IResourceProxy.java | 163 + .../core/resources/IResourceProxyVisitor.java | 52 + .../core/resources/IResourceRuleFactory.java | 162 + .../core/resources/IResourceStatus.java | 322 ++ .../core/resources/IResourceVisitor.java | 46 + .../eclipse/core/resources/ISaveContext.java | 197 ++ .../core/resources/ISaveParticipant.java | 172 + .../eclipse/core/resources/ISavedState.java | 87 + .../org/eclipse/core/resources/IStorage.java | 68 + .../eclipse/core/resources/ISynchronizer.java | 135 + .../eclipse/core/resources/IWorkspace.java | 1764 +++++++++++ .../core/resources/IWorkspaceDescription.java | 268 ++ .../core/resources/IWorkspaceRoot.java | 402 +++ .../core/resources/IWorkspaceRunnable.java | 35 + .../resources/IncrementalProjectBuilder.java | 472 +++ .../eclipse/core/resources/ProjectScope.java | 105 + .../core/resources/ResourceAttributes.java | 234 ++ .../core/resources/ResourcesPlugin.java | 485 +++ .../eclipse/core/resources/WorkspaceJob.java | 83 + .../AbstractFileInfoMatcher.java | 49 + .../CompoundFileInfoMatcher.java | 43 + .../resources/filtermatchers/package.html | 21 + .../mapping/CompositeResourceMapping.java | 97 + .../mapping/IModelProviderDescriptor.java | 98 + .../IResourceChangeDescriptionFactory.java | 84 + .../core/resources/mapping/ModelProvider.java | 259 ++ .../core/resources/mapping/ModelStatus.java | 56 + .../mapping/RemoteResourceMappingContext.java | 321 ++ .../mapping/ResourceChangeValidator.java | 169 + .../resources/mapping/ResourceMapping.java | 205 ++ .../mapping/ResourceMappingContext.java | 45 + .../resources/mapping/ResourceTraversal.java | 183 ++ .../core/resources/mapping/package.html | 27 + .../org/eclipse/core/resources/package.html | 25 + .../resources/refresh/IRefreshMonitor.java | 37 + .../resources/refresh/IRefreshResult.java | 49 + .../resources/refresh/RefreshProvider.java | 86 + .../core/resources/refresh/package.html | 23 + .../FileModificationValidationContext.java | 56 + .../team/FileModificationValidator.java | 102 + .../core/resources/team/IMoveDeleteHook.java | 383 +++ .../core/resources/team/IResourceTree.java | 383 +++ .../resources/team/ResourceRuleFactory.java | 274 ++ .../eclipse/core/resources/team/TeamHook.java | 231 ++ .../eclipse/core/resources/team/package.html | 19 + .../PathVariableResolver.java | 52 + .../resources/variableresolvers/package.html | 21 + third_party/patches/neon/pom.xml | 21 + .../org.eclipse.core.resources/.classpath | 7 + .../org.eclipse.core.resources/.project | 17 + .../META-INF/MANIFEST.MF | 32 + .../OSGI-INF/l10n/bundle-src.properties | 4 + .../org.eclipse.core.resources/about.html | 28 + .../resources-antsrc/META-INF/eclipse.inf | 2 + .../core/resources/ant/ConvertPath.java | 176 ++ .../core/resources/ant/IncrementalBuild.java | 126 + .../eclipse/core/resources/ant/Policy.java | 71 + .../core/resources/ant/RefreshLocalTask.java | 120 + .../core/resources/ant/messages.properties | 22 + .../build.properties | 22 + .../natives/make.bat | 29 + .../natives/readme.txt | 3 + .../org.eclipse.core.resources/natives/ref.c | 308 ++ .../org.eclipse.core.resources/natives/ref.h | 221 ++ .../plugin.properties | 40 + .../org.eclipse.core.resources/plugin.xml | 260 ++ .../schema/builders.exsd | 234 ++ .../schema/fileModificationValidator.exsd | 111 + .../schema/filterMatchers.exsd | 187 ++ .../schema/markers.exsd | 167 + .../schema/modelProviders.exsd | 159 + .../schema/moveDeleteHook.exsd | 120 + .../schema/natures.exsd | 334 ++ .../schema/refreshProviders.exsd | 133 + .../schema/teamHook.exsd | 120 + .../schema/variableResolvers.exsd | 139 + .../core/internal/dtree/AbstractDataTree.java | 330 ++ .../internal/dtree/AbstractDataTreeNode.java | 598 ++++ .../core/internal/dtree/DataDeltaNode.java | 121 + .../eclipse/core/internal/dtree/DataTree.java | 294 ++ .../core/internal/dtree/DataTreeLookup.java | 80 + .../core/internal/dtree/DataTreeNode.java | 366 +++ .../core/internal/dtree/DataTreeReader.java | 150 + .../core/internal/dtree/DataTreeWriter.java | 184 ++ .../core/internal/dtree/DeletedNode.java | 131 + .../core/internal/dtree/DeltaDataTree.java | 977 ++++++ .../core/internal/dtree/IComparator.java | 28 + .../core/internal/dtree/IDataFlattener.java | 39 + .../core/internal/dtree/NoDataDeltaNode.java | 133 + .../core/internal/dtree/NodeComparison.java | 136 + .../dtree/ObjectNotFoundException.java | 30 + .../core/internal/dtree/TestHelper.java | 23 + .../core/internal/events/AutoBuildJob.java | 273 ++ .../core/internal/events/BuildCommand.java | 291 ++ .../core/internal/events/BuildContext.java | 120 + .../core/internal/events/BuildManager.java | 1158 +++++++ .../events/BuilderPersistentInfo.java | 81 + .../internal/events/ILifecycleListener.java | 21 + .../core/internal/events/InternalBuilder.java | 248 ++ .../core/internal/events/LifecycleEvent.java | 91 + .../core/internal/events/NodeIDMap.java | 221 ++ .../internal/events/NotificationManager.java | 341 ++ .../events/PathVariableChangeEvent.java | 93 + .../internal/events/ResourceChangeEvent.java | 156 + .../events/ResourceChangeListenerList.java | 180 ++ .../internal/events/ResourceComparator.java | 200 ++ .../core/internal/events/ResourceDelta.java | 552 ++++ .../internal/events/ResourceDeltaFactory.java | 184 ++ .../internal/events/ResourceDeltaInfo.java | 58 + .../core/internal/events/ResourceStats.java | 96 + .../core/internal/localstore/BlobStore.java | 138 + .../core/internal/localstore/Bucket.java | 396 +++ .../core/internal/localstore/BucketTree.java | 160 + .../localstore/CollectSyncStatusVisitor.java | 117 + .../core/internal/localstore/CopyVisitor.java | 211 ++ .../internal/localstore/DeleteVisitor.java | 154 + .../internal/localstore/FileStoreRoot.java | 201 ++ .../localstore/FileSystemResourceManager.java | 1236 ++++++++ .../internal/localstore/HistoryBucket.java | 326 ++ .../internal/localstore/HistoryStore2.java | 383 +++ .../internal/localstore/IHistoryStore.java | 178 ++ .../localstore/ILocalStoreConstants.java | 31 + .../localstore/IUnifiedTreeVisitor.java | 20 + .../localstore/IsSynchronizedVisitor.java | 59 + .../core/internal/localstore/PrefixPool.java | 207 ++ .../localstore/RefreshLocalAliasVisitor.java | 110 + .../localstore/RefreshLocalVisitor.java | 314 ++ .../localstore/SafeChunkyInputStream.java | 184 ++ .../localstore/SafeChunkyOutputStream.java | 81 + .../localstore/SafeFileInputStream.java | 53 + .../localstore/SafeFileOutputStream.java | 141 + .../core/internal/localstore/UnifiedTree.java | 575 ++++ .../internal/localstore/UnifiedTreeNode.java | 149 + .../internal/properties/IPropertyManager.java | 75 + .../internal/properties/PropertyBucket.java | 350 ++ .../internal/properties/PropertyManager2.java | 192 ++ .../propertytester/FilePropertyTester.java | 117 + .../propertytester/ProjectPropertyTester.java | 34 + .../ResourceMappingPropertyTester.java | 70 + .../ResourcePropertyTester.java | 221 ++ .../propertytester/StringMatcher.java | 231 ++ .../refresh/InternalRefreshProvider.java | 48 + .../core/internal/refresh/MonitorJob.java | 133 + .../core/internal/refresh/MonitorManager.java | 378 +++ .../core/internal/refresh/PollingMonitor.java | 219 ++ .../core/internal/refresh/RefreshJob.java | 217 ++ .../core/internal/refresh/RefreshManager.java | 126 + .../core/internal/resources/AliasManager.java | 762 +++++ .../resources/BuildConfiguration.java | 118 + .../internal/resources/CharsetDeltaJob.java | 212 ++ .../internal/resources/CharsetManager.java | 502 +++ .../resources/ComputeProjectOrder.java | 621 ++++ .../core/internal/resources/Container.java | 387 +++ .../resources/ContentDescriptionManager.java | 549 ++++ .../resources/DelayedSnapshotJob.java | 60 + .../eclipse/core/internal/resources/File.java | 444 +++ .../core/internal/resources/FileState.java | 108 + .../core/internal/resources/Filter.java | 177 ++ .../internal/resources/FilterDescription.java | 144 + .../internal/resources/FilterDescriptor.java | 77 + .../internal/resources/FilterTypeManager.java | 106 + .../core/internal/resources/Folder.java | 186 ++ .../internal/resources/ICoreConstants.java | 125 + .../core/internal/resources/IManager.java | 20 + .../internal/resources/IMarkerSetElement.java | 15 + .../resources/IModelObjectConstants.java | 58 + .../internal/resources/InternalTeamHook.java | 33 + .../resources/InternalWorkspaceJob.java | 54 + .../internal/resources/LinkDescription.java | 126 + .../internal/resources/LocalMetaArea.java | 514 +++ .../internal/resources/LocationValidator.java | 438 +++ .../core/internal/resources/Marker.java | 372 +++ .../resources/MarkerAttributeMap.java | 295 ++ .../core/internal/resources/MarkerDelta.java | 209 ++ .../resources/MarkerDeltaManager.java | 92 + .../core/internal/resources/MarkerInfo.java | 205 ++ .../internal/resources/MarkerManager.java | 669 ++++ .../core/internal/resources/MarkerReader.java | 57 + .../internal/resources/MarkerReader_1.java | 146 + .../internal/resources/MarkerReader_2.java | 147 + .../internal/resources/MarkerReader_3.java | 163 + .../core/internal/resources/MarkerSet.java | 247 ++ .../resources/MarkerSnapshotReader.java | 51 + .../resources/MarkerSnapshotReader_1.java | 130 + .../resources/MarkerSnapshotReader_2.java | 145 + .../resources/MarkerTypeDefinitionCache.java | 142 + .../core/internal/resources/MarkerWriter.java | 218 ++ .../core/internal/resources/ModelObject.java | 40 + .../internal/resources/ModelObjectWriter.java | 306 ++ .../internal/resources/MoveDeleteHook.java | 77 + .../internal/resources/NatureManager.java | 643 ++++ .../eclipse/core/internal/resources/OS.java | 78 + .../resources/PathVariableManager.java | 390 +++ .../internal/resources/PathVariableUtil.java | 504 +++ .../PlatformURLResourceConnection.java | 108 + .../resources/PreferenceInitializer.java | 78 + .../core/internal/resources/Project.java | 1385 ++++++++ .../resources/ProjectContentTypes.java | 245 ++ .../resources/ProjectDescription.java | 960 ++++++ .../resources/ProjectDescriptionReader.java | 1119 +++++++ .../core/internal/resources/ProjectInfo.java | 141 + .../resources/ProjectNatureDescriptor.java | 168 + .../resources/ProjectPathVariableManager.java | 502 +++ .../resources/ProjectPreferences.java | 689 ++++ .../ProjectVariableProviderManager.java | 119 + .../resources/RegexFileInfoMatcher.java | 51 + .../core/internal/resources/Resource.java | 2033 ++++++++++++ .../internal/resources/ResourceException.java | 98 + .../core/internal/resources/ResourceInfo.java | 477 +++ .../internal/resources/ResourceProxy.java | 134 + .../internal/resources/ResourceStatus.java | 85 + .../core/internal/resources/ResourceTree.java | 1165 +++++++ .../core/internal/resources/RootInfo.java | 53 + .../core/internal/resources/Rules.java | 230 ++ .../internal/resources/SafeFileTable.java | 99 + .../core/internal/resources/SaveContext.java | 134 + .../core/internal/resources/SaveManager.java | 2139 +++++++++++++ .../core/internal/resources/SavedState.java | 90 + .../internal/resources/SyncInfoReader.java | 81 + .../internal/resources/SyncInfoReader_2.java | 99 + .../internal/resources/SyncInfoReader_3.java | 99 + .../resources/SyncInfoSnapReader.java | 52 + .../resources/SyncInfoSnapReader_3.java | 65 + .../internal/resources/SyncInfoWriter.java | 121 + .../core/internal/resources/Synchronizer.java | 285 ++ .../internal/resources/TestingSupport.java | 49 + .../resources/VariableDescription.java | 73 + .../internal/resources/VirtualFileStore.java | 80 + .../internal/resources/VirtualFileSystem.java | 31 + .../core/internal/resources/WorkManager.java | 325 ++ .../core/internal/resources/Workspace.java | 2571 +++++++++++++++ .../resources/WorkspaceDescription.java | 201 ++ .../resources/WorkspaceDescriptionReader.java | 169 + .../resources/WorkspacePreferences.java | 257 ++ .../internal/resources/WorkspaceRoot.java | 295 ++ .../resources/WorkspaceTreeReader.java | 84 + .../resources/WorkspaceTreeReader_1.java | 271 ++ .../resources/WorkspaceTreeReader_2.java | 219 ++ .../core/internal/resources/XMLWriter.java | 130 + .../resources/mapping/ChangeDescription.java | 135 + .../mapping/ModelProviderDescriptor.java | 146 + .../mapping/ModelProviderManager.java | 78 + .../mapping/ProposedResourceDelta.java | 182 ++ .../mapping/ResourceAdapterFactory.java | 36 + .../ResourceChangeDescriptionFactory.java | 240 ++ .../mapping/ResourceModelProvider.java | 73 + .../resources/mapping/ShallowContainer.java | 56 + .../mapping/ShallowResourceMapping.java | 68 + .../mapping/SimpleResourceMapping.java | 71 + .../EclipseHomeProjectVariable.java | 47 + .../ParentVariableResolver.java | 64 + .../ProjectLocationVariableResolver.java | 39 + .../WorkspaceLocationVariableResolver.java | 36 + ...rkspaceParentLocationVariableResolver.java | 45 + .../resources/refresh/win32/Convert.java | 50 + .../resources/refresh/win32/Win32Monitor.java | 631 ++++ .../resources/refresh/win32/Win32Natives.java | 361 +++ .../refresh/win32/Win32RefreshProvider.java | 41 + .../core/internal/utils/ArrayIterator.java | 65 + .../eclipse/core/internal/utils/BitMask.java | 23 + .../eclipse/core/internal/utils/Cache.java | 207 ++ .../eclipse/core/internal/utils/Convert.java | 74 + .../eclipse/core/internal/utils/FileUtil.java | 436 +++ .../utils/IStringPoolParticipant.java | 32 + .../core/internal/utils/KeyedHashSet.java | 243 ++ .../eclipse/core/internal/utils/Messages.java | 333 ++ .../core/internal/utils/ObjectMap.java | 330 ++ .../eclipse/core/internal/utils/Policy.java | 180 ++ .../eclipse/core/internal/utils/Queue.java | 201 ++ .../core/internal/utils/StringPool.java | 76 + .../core/internal/utils/StringPoolJob.java | 137 + .../utils/UniversalUniqueIdentifier.java | 359 +++ .../utils/WrappedRuntimeException.java | 35 + .../core/internal/utils/messages.properties | 327 ++ .../watson/DefaultElementComparator.java | 64 + .../core/internal/watson/ElementTree.java | 732 +++++ .../internal/watson/ElementTreeIterator.java | 169 + .../internal/watson/ElementTreeReader.java | 155 + .../watson/ElementTreeReaderImpl_1.java | 101 + .../internal/watson/ElementTreeWriter.java | 242 ++ .../internal/watson/IElementComparator.java | 25 + .../watson/IElementContentVisitor.java | 30 + .../watson/IElementInfoFlattener.java | 39 + .../internal/watson/IElementTreeData.java | 22 + .../core/internal/watson/IPathRequestor.java | 23 + .../resources/FileInfoMatcherDescription.java | 67 + .../core/resources/IBuildConfiguration.java | 82 + .../eclipse/core/resources/IBuildContext.java | 63 + .../org/eclipse/core/resources/ICommand.java | 152 + .../eclipse/core/resources/IContainer.java | 562 ++++ .../resources/IDynamicReferenceProvider.java | 34 + .../core/resources/IEncodedStorage.java | 44 + .../src/org/eclipse/core/resources/IFile.java | 1162 +++++++ .../resources/IFileModificationValidator.java | 68 + .../eclipse/core/resources/IFileState.java | 121 + .../resources/IFilterMatcherDescriptor.java | 84 + .../org/eclipse/core/resources/IFolder.java | 461 +++ .../org/eclipse/core/resources/IMarker.java | 578 ++++ .../eclipse/core/resources/IMarkerDelta.java | 180 ++ .../resources/IPathVariableChangeEvent.java | 72 + .../IPathVariableChangeListener.java | 40 + .../core/resources/IPathVariableManager.java | 352 +++ .../org/eclipse/core/resources/IProject.java | 1067 +++++++ .../core/resources/IProjectDescription.java | 387 +++ .../core/resources/IProjectNature.java | 81 + .../resources/IProjectNatureDescriptor.java | 88 + .../org/eclipse/core/resources/IResource.java | 2790 ++++++++++++++++ .../core/resources/IResourceChangeEvent.java | 266 ++ .../resources/IResourceChangeListener.java | 47 + .../core/resources/IResourceDelta.java | 591 ++++ .../core/resources/IResourceDeltaVisitor.java | 55 + .../resources/IResourceFilterDescription.java | 119 + .../core/resources/IResourceProxy.java | 163 + .../core/resources/IResourceProxyVisitor.java | 52 + .../core/resources/IResourceRuleFactory.java | 162 + .../core/resources/IResourceStatus.java | 322 ++ .../core/resources/IResourceVisitor.java | 46 + .../eclipse/core/resources/ISaveContext.java | 197 ++ .../core/resources/ISaveParticipant.java | 172 + .../eclipse/core/resources/ISavedState.java | 87 + .../org/eclipse/core/resources/IStorage.java | 68 + .../eclipse/core/resources/ISynchronizer.java | 135 + .../eclipse/core/resources/IWorkspace.java | 1764 +++++++++++ .../core/resources/IWorkspaceDescription.java | 268 ++ .../core/resources/IWorkspaceRoot.java | 402 +++ .../core/resources/IWorkspaceRunnable.java | 35 + .../resources/IncrementalProjectBuilder.java | 472 +++ .../eclipse/core/resources/ProjectScope.java | 99 + .../core/resources/ResourceAttributes.java | 234 ++ .../core/resources/ResourcesPlugin.java | 488 +++ .../eclipse/core/resources/WorkspaceJob.java | 83 + .../AbstractFileInfoMatcher.java | 49 + .../CompoundFileInfoMatcher.java | 43 + .../resources/filtermatchers/package.html | 21 + .../mapping/CompositeResourceMapping.java | 97 + .../mapping/IModelProviderDescriptor.java | 98 + .../IResourceChangeDescriptionFactory.java | 84 + .../core/resources/mapping/ModelProvider.java | 259 ++ .../core/resources/mapping/ModelStatus.java | 56 + .../mapping/RemoteResourceMappingContext.java | 321 ++ .../mapping/ResourceChangeValidator.java | 169 + .../resources/mapping/ResourceMapping.java | 205 ++ .../mapping/ResourceMappingContext.java | 45 + .../resources/mapping/ResourceTraversal.java | 183 ++ .../core/resources/mapping/package.html | 27 + .../org/eclipse/core/resources/package.html | 25 + .../resources/refresh/IRefreshMonitor.java | 37 + .../resources/refresh/IRefreshResult.java | 49 + .../resources/refresh/RefreshProvider.java | 119 + .../core/resources/refresh/package.html | 23 + .../FileModificationValidationContext.java | 56 + .../team/FileModificationValidator.java | 102 + .../core/resources/team/IMoveDeleteHook.java | 383 +++ .../core/resources/team/IResourceTree.java | 383 +++ .../resources/team/ResourceRuleFactory.java | 274 ++ .../eclipse/core/resources/team/TeamHook.java | 231 ++ .../eclipse/core/resources/team/package.html | 19 + .../PathVariableResolver.java | 52 + .../resources/variableresolvers/package.html | 21 + third_party/patches/oxygen/pom.xml | 21 + 935 files changed, 219781 insertions(+) create mode 100644 .mvn/extensions.xml create mode 100644 third_party/patches/README.md create mode 100644 third_party/patches/mars/org.eclipse.core.resources/.classpath create mode 100644 third_party/patches/mars/org.eclipse.core.resources/.project create mode 100644 third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF create mode 100644 third_party/patches/mars/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties create mode 100644 third_party/patches/mars/org.eclipse.core.resources/about.html create mode 100644 third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/META-INF/eclipse.inf create mode 100644 third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/ConvertPath.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/IncrementalBuild.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/Policy.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/RefreshLocalTask.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/messages.properties create mode 100644 third_party/patches/mars/org.eclipse.core.resources/build.properties create mode 100644 third_party/patches/mars/org.eclipse.core.resources/natives/make.bat create mode 100644 third_party/patches/mars/org.eclipse.core.resources/natives/readme.txt create mode 100644 third_party/patches/mars/org.eclipse.core.resources/natives/ref.c create mode 100644 third_party/patches/mars/org.eclipse.core.resources/natives/ref.h create mode 100644 third_party/patches/mars/org.eclipse.core.resources/plugin.properties create mode 100644 third_party/patches/mars/org.eclipse.core.resources/plugin.xml create mode 100644 third_party/patches/mars/org.eclipse.core.resources/schema/builders.exsd create mode 100644 third_party/patches/mars/org.eclipse.core.resources/schema/fileModificationValidator.exsd create mode 100644 third_party/patches/mars/org.eclipse.core.resources/schema/filterMatchers.exsd create mode 100644 third_party/patches/mars/org.eclipse.core.resources/schema/markers.exsd create mode 100644 third_party/patches/mars/org.eclipse.core.resources/schema/modelProviders.exsd create mode 100644 third_party/patches/mars/org.eclipse.core.resources/schema/moveDeleteHook.exsd create mode 100644 third_party/patches/mars/org.eclipse.core.resources/schema/natures.exsd create mode 100644 third_party/patches/mars/org.eclipse.core.resources/schema/refreshProviders.exsd create mode 100644 third_party/patches/mars/org.eclipse.core.resources/schema/teamHook.exsd create mode 100644 third_party/patches/mars/org.eclipse.core.resources/schema/variableResolvers.exsd create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTree.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeLookup.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NodeComparison.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/TestHelper.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ILifecycleListener.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/LifecycleEvent.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NodeIDMap.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaInfo.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/ILocalStoreConstants.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IUnifiedTreeVisitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyOutputStream.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Filter.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IMarkerSetElement.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalWorkspaceJob.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSet.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildConfiguration.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java create mode 100644 third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html create mode 100644 third_party/patches/mars/pom.xml create mode 100644 third_party/patches/neon/org.eclipse.core.resources/.classpath create mode 100644 third_party/patches/neon/org.eclipse.core.resources/.project create mode 100644 third_party/patches/neon/org.eclipse.core.resources/META-INF/MANIFEST.MF create mode 100644 third_party/patches/neon/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties create mode 100644 third_party/patches/neon/org.eclipse.core.resources/about.html create mode 100644 third_party/patches/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/META-INF/eclipse.inf create mode 100644 third_party/patches/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/ConvertPath.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/IncrementalBuild.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/Policy.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/RefreshLocalTask.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/messages.properties create mode 100644 third_party/patches/neon/org.eclipse.core.resources/build.properties create mode 100644 third_party/patches/neon/org.eclipse.core.resources/natives/make.bat create mode 100644 third_party/patches/neon/org.eclipse.core.resources/natives/readme.txt create mode 100644 third_party/patches/neon/org.eclipse.core.resources/natives/ref.c create mode 100644 third_party/patches/neon/org.eclipse.core.resources/natives/ref.h create mode 100644 third_party/patches/neon/org.eclipse.core.resources/plugin.properties create mode 100644 third_party/patches/neon/org.eclipse.core.resources/plugin.xml create mode 100644 third_party/patches/neon/org.eclipse.core.resources/schema/builders.exsd create mode 100644 third_party/patches/neon/org.eclipse.core.resources/schema/fileModificationValidator.exsd create mode 100644 third_party/patches/neon/org.eclipse.core.resources/schema/filterMatchers.exsd create mode 100644 third_party/patches/neon/org.eclipse.core.resources/schema/markers.exsd create mode 100644 third_party/patches/neon/org.eclipse.core.resources/schema/modelProviders.exsd create mode 100644 third_party/patches/neon/org.eclipse.core.resources/schema/moveDeleteHook.exsd create mode 100644 third_party/patches/neon/org.eclipse.core.resources/schema/natures.exsd create mode 100644 third_party/patches/neon/org.eclipse.core.resources/schema/refreshProviders.exsd create mode 100644 third_party/patches/neon/org.eclipse.core.resources/schema/teamHook.exsd create mode 100644 third_party/patches/neon/org.eclipse.core.resources/schema/variableResolvers.exsd create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTree.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeLookup.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NodeComparison.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/TestHelper.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ILifecycleListener.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/LifecycleEvent.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NodeIDMap.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaInfo.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/ILocalStoreConstants.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IUnifiedTreeVisitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyOutputStream.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Filter.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IMarkerSetElement.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalWorkspaceJob.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSet.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildConfiguration.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java create mode 100644 third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html create mode 100644 third_party/patches/neon/pom.xml create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/.classpath create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/.project create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/META-INF/MANIFEST.MF create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/about.html create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/META-INF/eclipse.inf create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/ConvertPath.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/IncrementalBuild.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/Policy.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/RefreshLocalTask.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/messages.properties create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/build.properties create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/natives/make.bat create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/natives/readme.txt create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/natives/ref.c create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/natives/ref.h create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/plugin.properties create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/plugin.xml create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/schema/builders.exsd create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/schema/fileModificationValidator.exsd create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/schema/filterMatchers.exsd create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/schema/markers.exsd create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/schema/modelProviders.exsd create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/schema/moveDeleteHook.exsd create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/schema/natures.exsd create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/schema/refreshProviders.exsd create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/schema/teamHook.exsd create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/schema/variableResolvers.exsd create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTree.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeLookup.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NodeComparison.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/TestHelper.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ILifecycleListener.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/LifecycleEvent.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NodeIDMap.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaInfo.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/ILocalStoreConstants.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IUnifiedTreeVisitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyOutputStream.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorJob.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Filter.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IMarkerSetElement.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalWorkspaceJob.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSet.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildConfiguration.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IDynamicReferenceProvider.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html create mode 100644 third_party/patches/oxygen/pom.xml diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 0000000000..741850139a --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,10 @@ + + + + org.eclipse.tycho.extras + tycho-pomless + 1.0.0 + + + + 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.resources/.classpath b/third_party/patches/mars/org.eclipse.core.resources/.classpath new file mode 100644 index 0000000000..1582404385 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/third_party/patches/mars/org.eclipse.core.resources/.project b/third_party/patches/mars/org.eclipse.core.resources/.project new file mode 100644 index 0000000000..5b64045589 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/.project @@ -0,0 +1,17 @@ + + + org.eclipse.core.resources + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF b/third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..917e0393bb --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF @@ -0,0 +1,33 @@ +Manifest-Version: 1.0 +Comment: Bundle-Version: 3.10.1.v20150725-2910 +Bundle-Version: 3.10.1.v20150725-1910 +Bundle-Localization: plugin +Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true +Require-Bundle: org.eclipse.ant.core;bundle-version="[3.1.0,4.0.0)";resolution:=optional,org.eclipse.core.expressions;bundle-version="[3.2.0,4.0.0)", + org.eclipse.core.filesystem;bundle-version="[1.3.0,2.0.0)", + org.eclipse.core.runtime;bundle-version="[3.7.0,4.0.0)" +Bundle-ManifestVersion: 2 +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-Vendor: %providerName +Export-Package: org.eclipse.core.internal.dtree;x-internal:=true, + org.eclipse.core.internal.events;x-internal:=true, + org.eclipse.core.internal.localstore;x-internal:=true, + org.eclipse.core.internal.properties;x-internal:=true, + org.eclipse.core.internal.propertytester;x-internal:=true, + org.eclipse.core.internal.refresh;x-internal:=true, + org.eclipse.core.internal.resources;x-internal:=true, + org.eclipse.core.internal.resources.mapping;x-internal:=true, + org.eclipse.core.internal.resources.projectvariables;x-internal:=true, + org.eclipse.core.internal.resources.refresh.win32;x-internal:=true, + org.eclipse.core.internal.utils;x-internal:=true, + org.eclipse.core.internal.watson;x-internal:=true, + org.eclipse.core.resources, + org.eclipse.core.resources.filtermatchers, + org.eclipse.core.resources.mapping, + org.eclipse.core.resources.refresh, + org.eclipse.core.resources.team, + org.eclipse.core.resources.variableresolvers +Bundle-Name: %pluginName +Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin + diff --git a/third_party/patches/mars/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties b/third_party/patches/mars/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties new file mode 100644 index 0000000000..4096913532 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties @@ -0,0 +1,4 @@ +#Source Bundle Localization +#Wed Aug 05 10:46:08 EDT 2015 +bundleVendor=Eclipse.org +bundleName=Core Resource Management Source diff --git a/third_party/patches/mars/org.eclipse.core.resources/about.html b/third_party/patches/mars/org.eclipse.core.resources/about.html new file mode 100644 index 0000000000..460233046e --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/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.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 &quot;false&quot;, 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: + *

+ *

+ * 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: + *

+ * 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. + *

+ * Version 2 (3.1 M5): + *

+	 * FILE ::= VERSION_ID ENTRY+
+	 * ENTRY ::= PATH STATE_COUNT STATE+
+	 * PATH ::= string (does not include project name)
+	 * STATE_COUNT ::= int
+	 * STATE ::= UUID LAST_MODIFIED
+	 * UUID	 ::= byte[16]
+	 * LAST_MODIFIED ::= byte[8]
+	 * 
+ *

+ *

+ * Version 1 (3.1 M4): + *

+	 * FILE ::= VERSION_ID ENTRY+
+	 * ENTRY ::= PATH STATE_COUNT STATE+
+	 * PATH ::= string
+	 * STATE_COUNT ::= int
+	 * STATE ::= UUID LAST_MODIFIED
+	 * UUID	 ::= byte[16]
+	 * LAST_MODIFIED ::= byte[8]
+	 * 
+ *

+ */ + 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. + *

+ * Version 1: + *

+	 * FILE ::= VERSION_ID ENTRY+
+	 * ENTRY ::= PATH PROPERTY_COUNT PROPERTY+
+	 * PATH ::= string (does not contain project name)
+	 * PROPERTY_COUNT ::= int
+	 * PROPERTY ::= QUALIFIER LOCAL_NAME VALUE
+	 * QUALIFIER ::= INDEX | QNAME
+	 * INDEX -> byte int
+	 * QNAME -> byte string   
+	 * UUID ::= byte[16]
+	 * LAST_MODIFIED ::= byte[8]
+	 * 
+ *

+ */ + 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 vertexes, List 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 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 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 map) { + for (Map.Entry 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(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(); + 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/MarkerTypeDefinitionCache.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java new file mode 100644 index 0000000000..25bc903109 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * 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.resources; + +import java.util.*; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; + +public class MarkerTypeDefinitionCache { + static class MarkerTypeDefinition { + boolean isPersistent = false; + Set superTypes; + + MarkerTypeDefinition(IExtension ext) { + IConfigurationElement[] elements = ext.getConfigurationElements(); + for (int i = 0; i < elements.length; i++) { + IConfigurationElement element = elements[i]; + // supertype + final String elementName = element.getName(); + if (elementName.equalsIgnoreCase("super")) { //$NON-NLS-1$ + String aType = element.getAttribute("type"); //$NON-NLS-1$ + if (aType != null) { + if (superTypes == null) + superTypes = new HashSet(8); + //note that all marker type names will be in the intern table + //already because there is invariably a constant to describe + //the type name + superTypes.add(aType.intern()); + } + } + // persistent + if (elementName.equalsIgnoreCase("persistent")) { //$NON-NLS-1$ + String bool = element.getAttribute("value"); //$NON-NLS-1$ + if (bool != null) + this.isPersistent = Boolean.valueOf(bool).booleanValue(); + } + // XXX: legacy code for support of tag. remove later. + if (elementName.equalsIgnoreCase("transient")) { //$NON-NLS-1$ + String bool = element.getAttribute("value"); //$NON-NLS-1$ + if (bool != null) + this.isPersistent = !Boolean.valueOf(bool).booleanValue(); + } + } + } + } + + /** + * The marker type definitions. Maps String (markerId) -> MarkerTypeDefinition + */ + protected HashMap definitions; + + /** Constructs a new type cache. + */ + public MarkerTypeDefinitionCache() { + loadDefinitions(); + HashSet toCompute = new HashSet(definitions.keySet()); + for (Iterator i = definitions.keySet().iterator(); i.hasNext();) { + String markerId = i.next(); + if (toCompute.contains(markerId)) + computeSuperTypes(markerId, toCompute); + } + } + + /** + * Computes the transitive set of super types of the given marker type. + * @param markerId The type to compute super types for + * @param toCompute The set of types that have not yet had their + * supertypes computed. + * @return The transitive set of super types for this marker, or null + * if this marker is not defined or has no super types. + */ + private Set computeSuperTypes(String markerId, Set toCompute) { + MarkerTypeDefinition def = definitions.get(markerId); + if (def == null || def.superTypes == null) { + //nothing to do if there are no supertypes + toCompute.remove(markerId); + return null; + } + Set transitiveSuperTypes = new HashSet(def.superTypes); + for (Iterator it = def.superTypes.iterator(); it.hasNext();) { + String superId = it.next(); + Set toAdd = null; + if (toCompute.contains(superId)) { + //this type's super types have not been compute yet. Do recursive call + toAdd = computeSuperTypes(superId, toCompute); + } else { + // we have already computed this super type's super types (or it doesn't exist) + MarkerTypeDefinition parentDef = definitions.get(superId); + if (parentDef != null) + toAdd = parentDef.superTypes; + } + if (toAdd != null) + transitiveSuperTypes.addAll(toAdd); + } + def.superTypes = transitiveSuperTypes; + toCompute.remove(markerId); + return transitiveSuperTypes; + } + + /** + * Returns true if the given marker type is defined to be persistent. + */ + public boolean isPersistent(String type) { + MarkerTypeDefinition def = definitions.get(type); + return def != null && def.isPersistent; + } + + /** + * Returns true if the given target class has the specified type as a super type. + */ + public boolean isSubtype(String type, String superType) { + //types are considered super types of themselves + if (type.equals(superType)) + return true; + MarkerTypeDefinition def = definitions.get(type); + return def != null && def.superTypes != null && def.superTypes.contains(superType); + } + + private void loadDefinitions() { + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MARKERS); + IExtension[] types = point.getExtensions(); + definitions = new HashMap(types.length); + for (int i = 0; i < types.length; i++) { + String markerId = types[i].getUniqueIdentifier(); + if (markerId != null) + definitions.put(markerId.intern(), new MarkerTypeDefinition(types[i])); + else + Policy.log(IStatus.WARNING, "Missing marker id from plugin: " + types[i].getContributor().getName(), null); //$NON-NLS-1$ + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java new file mode 100644 index 0000000000..7b6241de7a --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java @@ -0,0 +1,216 @@ +/******************************************************************************* + * 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.resources; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; +import org.eclipse.core.internal.watson.IPathRequestor; + +// +public class MarkerWriter { + + protected MarkerManager manager; + + // version numbers + public static final int MARKERS_SAVE_VERSION = 3; + public static final int MARKERS_SNAP_VERSION = 2; + + // 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 MarkerWriter(MarkerManager manager) { + super(); + this.manager = manager; + } + + /** + * Returns an Object array of length 2. The first element is an Integer which is the number + * of persistent markers found. The second element is an array of boolean values, with a + * value of true meaning that the marker at that index is to be persisted. + */ + private Object[] filterMarkers(IMarkerSetElement[] markers) { + Object[] result = new Object[2]; + boolean[] isPersistent = new boolean[markers.length]; + int count = 0; + for (int i = 0; i < markers.length; i++) { + MarkerInfo info = (MarkerInfo) markers[i]; + if (manager.isPersistent(info)) { + isPersistent[i] = true; + count++; + } + } + result[0] = new Integer(count); + result[1] = isPersistent; + return result; + } + + /** + * 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 + * + */ + public void save(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List writtenTypes) throws IOException { + // phantom resources don't have markers + if (info.isSet(ICoreConstants.M_PHANTOM)) + return; + MarkerSet markers = info.getMarkers(false); + if (markers == null) + return; + IMarkerSetElement[] elements = markers.elements(); + // filter out the markers...determine if there are any persistent ones + Object[] result = filterMarkers(elements); + int count = ((Integer) result[0]).intValue(); + if (count == 0) + return; + // if this is the first set of markers that we have written, then + // write the version id for the file. + if (output.size() == 0) + output.writeInt(MARKERS_SAVE_VERSION); + boolean[] isPersistent = (boolean[]) result[1]; + output.writeUTF(requestor.requestPath().toString()); + output.writeInt(count); + for (int i = 0; i < elements.length; i++) + if (isPersistent[i]) + write((MarkerInfo) elements[i], output, writtenTypes); + } + + /** + * Snapshot the markers for the specified resource to the given output stream. + * + * 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 + */ + public void snap(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { + // phantom resources don't have markers + if (info.isSet(ICoreConstants.M_PHANTOM)) + return; + if (!info.isSet(ICoreConstants.M_MARKERS_SNAP_DIRTY)) + return; + MarkerSet markers = info.getMarkers(false); + if (markers == null) + return; + IMarkerSetElement[] elements = markers.elements(); + // filter out the markers...determine if there are any persistent ones + Object[] result = filterMarkers(elements); + int count = ((Integer) result[0]).intValue(); + // write the version id for the snapshot. + output.writeInt(MARKERS_SNAP_VERSION); + boolean[] isPersistent = (boolean[]) result[1]; + output.writeUTF(requestor.requestPath().toString()); + // always write out the count...even if its zero. this will help + // use pick up marker deletions from our snapshot. + output.writeInt(count); + List writtenTypes = new ArrayList(); + for (int i = 0; i < elements.length; i++) + if (isPersistent[i]) + write((MarkerInfo) elements[i], output, writtenTypes); + info.clear(ICoreConstants.M_MARKERS_SNAP_DIRTY); + } + + /* + * Write out the given marker attributes to the given output stream. + */ + private void write(Map attributes, DataOutputStream output) throws IOException { + output.writeShort(attributes.size()); + for (Map.Entry e : attributes.entrySet()) { + String key = e.getKey(); + output.writeUTF(key); + Object value = e.getValue(); + if (value instanceof Integer) { + output.writeByte(ATTRIBUTE_INTEGER); + output.writeInt(((Integer) value).intValue()); + continue; + } + if (value instanceof Boolean) { + output.writeByte(ATTRIBUTE_BOOLEAN); + output.writeBoolean(((Boolean) value).booleanValue()); + continue; + } + if (value instanceof String) { + output.writeByte(ATTRIBUTE_STRING); + output.writeUTF((String) value); + continue; + } + // otherwise we came across an attribute of an unknown type + // so just write out null since we don't know how to marshal it. + output.writeByte(ATTRIBUTE_NULL); + } + } + + private void write(MarkerInfo info, DataOutputStream output, List writtenTypes) throws IOException { + output.writeLong(info.getId()); + // if we have already written the type once, then write an integer + // constant to represent it instead to remove duplication + String type = info.getType(); + int index = writtenTypes.indexOf(type); + if (index == -1) { + output.writeByte(QNAME); + output.writeUTF(type); + writtenTypes.add(type); + } else { + output.writeByte(INDEX); + output.writeInt(index); + } + + // write out the size of the attribute table and + // then each attribute. + if (info.getAttributes(false) == null) { + output.writeShort(0); + } else + write(info.getAttributes(false), output); + + // write out the creation time + output.writeLong(info.getCreationTime()); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java new file mode 100644 index 0000000000..e60a839f17 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java @@ -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 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +public abstract class ModelObject implements Cloneable { + protected String name; + + public ModelObject() { + super(); + } + + public ModelObject(String name) { + setName(name); + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; // won't happen + } + } + + public String getName() { + return name; + } + + public void setName(String value) { + name = value; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java new file mode 100644 index 0000000000..b8afa9a3e8 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java @@ -0,0 +1,304 @@ +/******************************************************************************* + * 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 + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.internal.localstore.SafeFileOutputStream; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; + +public class ModelObjectWriter implements IModelObjectConstants { + + /** + * Returns the string representing the serialized set of build triggers for + * the given command + */ + private static String triggerString(BuildCommand command) { + StringBuffer buf = new StringBuffer(); + if (command.isBuilding(IncrementalProjectBuilder.AUTO_BUILD)) + buf.append(TRIGGER_AUTO).append(','); + if (command.isBuilding(IncrementalProjectBuilder.CLEAN_BUILD)) + buf.append(TRIGGER_CLEAN).append(','); + if (command.isBuilding(IncrementalProjectBuilder.FULL_BUILD)) + buf.append(TRIGGER_FULL).append(','); + if (command.isBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD)) + buf.append(TRIGGER_INCREMENTAL).append(','); + return buf.toString(); + } + + public ModelObjectWriter() { + super(); + } + + protected String[] getReferencedProjects(ProjectDescription description) { + IProject[] projects = description.getReferencedProjects(); + String[] result = new String[projects.length]; + for (int i = 0; i < projects.length; i++) + result[i] = projects[i].getName(); + return result; + } + + protected void write(BuildCommand command, XMLWriter writer) { + writer.startTag(BUILD_COMMAND, null); + if (command != null) { + writer.printSimpleTag(NAME, command.getName()); + if (shouldWriteTriggers(command)) + writer.printSimpleTag(BUILD_TRIGGERS, triggerString(command)); + write(ARGUMENTS, command.getArguments(false), writer); + } + writer.endTag(BUILD_COMMAND); + } + + /** + * Returns whether the build triggers for this command should be written. + */ + private boolean shouldWriteTriggers(BuildCommand command) { + //only write triggers if command is configurable and there exists a trigger + //that the builder does NOT respond to. I.e., don't write out on the default + //cases to avoid dirtying .project files unnecessarily. + if (!command.isConfigurable()) + return false; + return !command.isBuilding(IncrementalProjectBuilder.AUTO_BUILD) || !command.isBuilding(IncrementalProjectBuilder.CLEAN_BUILD) || !command.isBuilding(IncrementalProjectBuilder.FULL_BUILD) || !command.isBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD); + } + + protected void write(LinkDescription description, XMLWriter writer) { + writer.startTag(LINK, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getProjectRelativePath()); + writer.printSimpleTag(TYPE, Integer.toString(description.getType())); + //use ASCII string of URI to ensure spaces are encoded + writeLocation(description.getLocationURI(), writer); + } + writer.endTag(LINK); + } + + protected void write(IResourceFilterDescription description, XMLWriter writer) { + writer.startTag(FILTER, null); + if (description != null) { + writer.printSimpleTag(ID, new Long(((FilterDescription) description).getId())); + writer.printSimpleTag(NAME, description.getResource().getProjectRelativePath()); + writer.printSimpleTag(TYPE, Integer.toString(description.getType())); + if (description.getFileInfoMatcherDescription() != null) { + write(description.getFileInfoMatcherDescription(), writer); + } + } + writer.endTag(FILTER); + } + + protected void write(FileInfoMatcherDescription description, XMLWriter writer) { + writer.startTag(MATCHER, null); + writer.printSimpleTag(ID, description.getId()); + if (description.getArguments() != null) { + if (description.getArguments() instanceof String) { + writer.printSimpleTag(ARGUMENTS, description.getArguments()); + } else if (description.getArguments() instanceof FileInfoMatcherDescription[]) { + writer.startTag(ARGUMENTS, null); + FileInfoMatcherDescription[] array = (FileInfoMatcherDescription[]) description.getArguments(); + for (int i = 0; i < array.length; i++) { + write(array[i], writer); + } + writer.endTag(ARGUMENTS); + } else + writer.printSimpleTag(ARGUMENTS, ""); //$NON-NLS-1$ + } + writer.endTag(MATCHER); + } + + protected void write(VariableDescription description, XMLWriter writer) { + writer.startTag(VARIABLE, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getName()); + writer.printSimpleTag(VALUE, description.getValue()); + } + writer.endTag(VARIABLE); + } + + /** + * Writes a location to the XML writer. For backwards compatibility, + * local file system locations are written and read using a different tag + * from non-local file systems. + * @param location + * @param writer + */ + private void writeLocation(URI location, XMLWriter writer) { + if (EFS.SCHEME_FILE.equals(location.getScheme())) { + writer.printSimpleTag(LOCATION, FileUtil.toPath(location).toPortableString()); + } else { + writer.printSimpleTag(LOCATION_URI, location.toASCIIString()); + } + } + + /** + * The parameter tempLocation is a location to place our temp file (copy of the target one) + * to be used in case we could not successfully write the new file. + */ + public void write(Object object, IPath location, IPath tempLocation, String lineSeparator) throws IOException { + SafeFileOutputStream file = null; + String tempPath = tempLocation == null ? null : tempLocation.toOSString(); + try { + file = new SafeFileOutputStream(location.toOSString(), tempPath); + write(object, file, lineSeparator); + file.close(); + } finally { + FileUtil.safeClose(file); + } + } + + /** + * The OutputStream is closed in this method. + */ + public void write(Object object, OutputStream output, String lineSeparator) throws IOException { + try { + XMLWriter writer = new XMLWriter(output, lineSeparator); + write(object, writer); + writer.flush(); + writer.close(); + if (writer.checkError()) + throw new IOException(); + } finally { + FileUtil.safeClose(output); + } + } + + protected void write(Object obj, XMLWriter writer) throws IOException { + if (obj instanceof BuildCommand) { + write((BuildCommand) obj, writer); + return; + } + if (obj instanceof ProjectDescription) { + write((ProjectDescription) obj, writer); + return; + } + if (obj instanceof WorkspaceDescription) { + write((WorkspaceDescription) obj, writer); + return; + } + if (obj instanceof LinkDescription) { + write((LinkDescription) obj, writer); + return; + } + if (obj instanceof IResourceFilterDescription) { + write((IResourceFilterDescription) obj, writer); + return; + } + if (obj instanceof VariableDescription) { + write((VariableDescription) obj, writer); + return; + } + writer.printTabulation(); + writer.println(obj.toString()); + } + + protected void write(ProjectDescription description, XMLWriter writer) throws IOException { + writer.startTag(PROJECT_DESCRIPTION, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getName()); + String comment = description.getComment(); + writer.printSimpleTag(COMMENT, comment == null ? "" : comment); //$NON-NLS-1$ + URI snapshotLocation = description.getSnapshotLocationURI(); + if (snapshotLocation != null) { + writer.printSimpleTag(SNAPSHOT_LOCATION, snapshotLocation.toString()); + } + write(PROJECTS, PROJECT, getReferencedProjects(description), writer); + write(BUILD_SPEC, Arrays.asList(description.getBuildSpec(false)), writer); + write(NATURES, NATURE, description.getNatureIds(false), writer); + HashMap links = description.getLinks(); + if (links != null) { + // ensure consistent order of map elements + List sorted = new ArrayList(links.values()); + Collections.sort(sorted); + write(LINKED_RESOURCES, sorted, writer); + } + HashMap> filters = description.getFilters(); + if (filters != null) { + List sorted = new ArrayList(); + for (Iterator> it = filters.values().iterator(); it.hasNext();) { + List list = it.next(); + sorted.addAll(list); + } + Collections.sort(sorted); + write(FILTERED_RESOURCES, sorted, writer); + } + HashMap variables = description.getVariables(); + if (variables != null) { + List sorted = new ArrayList(variables.values()); + Collections.sort(sorted); + write(VARIABLE_LIST, sorted, writer); + } + } + writer.endTag(PROJECT_DESCRIPTION); + } + + protected void write(String name, Collection collection, XMLWriter writer) throws IOException { + writer.startTag(name, null); + for (Object o : collection) + write(o, writer); + writer.endTag(name); + } + + /** + * Write maps of (String, String). + */ + protected void write(String name, Map table, XMLWriter writer) { + writer.startTag(name, null); + if (table != null) { + // ensure consistent order of map elements + List sorted = new ArrayList(table.keySet()); + Collections.sort(sorted); + + for (Iterator it = sorted.iterator(); it.hasNext();) { + String key = it.next(); + Object value = table.get(key); + writer.startTag(DICTIONARY, null); + { + writer.printSimpleTag(KEY, key); + writer.printSimpleTag(VALUE, value); + } + writer.endTag(DICTIONARY); + } + } + writer.endTag(name); + } + + protected void write(String name, String elementTagName, String[] array, XMLWriter writer) { + writer.startTag(name, null); + for (int i = 0; i < array.length; i++) + writer.printSimpleTag(elementTagName, array[i]); + writer.endTag(name); + } + + protected void write(WorkspaceDescription description, XMLWriter writer) { + writer.startTag(WORKSPACE_DESCRIPTION, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getName()); + writer.printSimpleTag(AUTOBUILD, description.isAutoBuilding() ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$ + writer.printSimpleTag(SNAPSHOT_INTERVAL, new Long(description.getSnapshotInterval())); + writer.printSimpleTag(APPLY_FILE_STATE_POLICY, description.isApplyFileStatePolicy() ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$ + writer.printSimpleTag(FILE_STATE_LONGEVITY, new Long(description.getFileStateLongevity())); + writer.printSimpleTag(MAX_FILE_STATE_SIZE, new Long(description.getMaxFileStateSize())); + writer.printSimpleTag(MAX_FILE_STATES, new Integer(description.getMaxFileStates())); + String[] order = description.getBuildOrder(false); + if (order != null) + write(BUILD_ORDER, PROJECT, order, writer); + } + writer.endTag(WORKSPACE_DESCRIPTION); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java new file mode 100644 index 0000000000..4b369fa4a7 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IMoveDeleteHook; +import org.eclipse.core.resources.team.IResourceTree; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * @since 2.0 + */ +public class MoveDeleteHook implements IMoveDeleteHook { + + /** + * @see IMoveDeleteHook#deleteFile(IResourceTree, IFile, int, IProgressMonitor) + */ + @Override + public boolean deleteFile(IResourceTree tree, IFile file, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#deleteFolder(IResourceTree, IFolder, int, IProgressMonitor) + */ + @Override + public boolean deleteFolder(IResourceTree tree, IFolder folder, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#deleteProject(IResourceTree, IProject, int, IProgressMonitor) + */ + @Override + public boolean deleteProject(IResourceTree tree, IProject project, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#moveFile(IResourceTree, IFile, IFile, int, IProgressMonitor) + */ + @Override + public boolean moveFile(IResourceTree tree, IFile source, IFile destination, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#moveFolder(IResourceTree, IFolder, IFolder, int, IProgressMonitor) + */ + @Override + public boolean moveFolder(final IResourceTree tree, IFolder source, IFolder destination, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#moveProject(IResourceTree, IProject, IProjectDescription, int, IProgressMonitor) + */ + @Override + public boolean moveProject(IResourceTree tree, IProject source, IProjectDescription description, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java new file mode 100644 index 0000000000..55cc880192 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java @@ -0,0 +1,642 @@ +/******************************************************************************* + * 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.ILifecycleListener; +import org.eclipse.core.internal.events.LifecycleEvent; +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; + +/** + * Maintains collection of known nature descriptors, and implements + * nature-related algorithms provided by the workspace. + */ +public class NatureManager implements ILifecycleListener, IManager { + //maps String (nature ID) -> descriptor objects + private Map descriptors; + + //maps IProject -> String[] of enabled natures for that project + private final Map natureEnablements = new HashMap(20); + + //maps String (builder ID) -> String (nature ID) + private Map buildersToNatures; + //colour constants used in cycle detection algorithm + private static final byte WHITE = 0; + private static final byte GREY = 1; + private static final byte BLACK = 2; + + protected NatureManager() { + super(); + } + + /** + * Computes the list of natures that are enabled for the given project. + * Enablement computation is subtly different from nature set + * validation, because it must find and remove all inconsistencies. + */ + protected String[] computeNatureEnablements(Project project) { + final ProjectDescription description = project.internalGetDescription(); + if (description == null) + return new String[0];//project deleted concurrently + String[] natureIds = description.getNatureIds(); + int count = natureIds.length; + if (count == 0) + return natureIds; + + //set of the nature ids being validated (String (id)) + HashSet candidates = new HashSet(count * 2); + //table of String(set ID) -> ArrayList (nature IDs that belong to that set) + HashMap> setsToNatures = new HashMap>(count); + for (int i = 0; i < count; i++) { + String id = natureIds[i]; + ProjectNatureDescriptor desc = (ProjectNatureDescriptor) getNatureDescriptor(id); + if (desc == null) + continue; + if (!desc.hasCycle) + candidates.add(id); + //build set to nature map + String[] setIds = desc.getNatureSetIds(); + for (int j = 0; j < setIds.length; j++) { + String set = setIds[j]; + ArrayList current = setsToNatures.get(set); + if (current == null) { + current = new ArrayList(5); + setsToNatures.put(set, current); + } + current.add(id); + } + } + //now remove all natures that belong to sets with more than one member + for (Iterator> it = setsToNatures.values().iterator(); it.hasNext();) { + ArrayList setMembers = it.next(); + if (setMembers.size() > 1) { + candidates.removeAll(setMembers); + } + } + //now walk over the set and ensure all pre-requisite natures are present + //need to walk in prereq order because if A requires B and B requires C, and C is + //disabled for some other reason, we must ensure both A and B are disabled + String[] orderedCandidates = candidates.toArray(new String[candidates.size()]); + orderedCandidates = sortNatureSet(orderedCandidates); + for (int i = 0; i < orderedCandidates.length; i++) { + String id = orderedCandidates[i]; + IProjectNatureDescriptor desc = getNatureDescriptor(id); + String[] required = desc.getRequiredNatureIds(); + for (int j = 0; j < required.length; j++) { + if (!candidates.contains(required[j])) { + candidates.remove(id); + break; + } + } + } + //remaining candidates are enabled + return candidates.toArray(new String[candidates.size()]); + } + + /* (non-Javadoc) + * @see IWorkspace#getNatureDescriptor(String) + */ + public synchronized IProjectNatureDescriptor getNatureDescriptor(String natureId) { + lazyInitialize(); + return descriptors.get(natureId); + } + + /* (non-Javadoc) + * @see IWorkspace#getNatureDescriptors() + */ + public synchronized IProjectNatureDescriptor[] getNatureDescriptors() { + lazyInitialize(); + Collection values = descriptors.values(); + return values.toArray(new IProjectNatureDescriptor[values.size()]); + } + + @Override + public void handleEvent(LifecycleEvent event) { + switch (event.kind) { + case LifecycleEvent.POST_PROJECT_CHANGE : + case LifecycleEvent.PRE_PROJECT_CLOSE : + case LifecycleEvent.PRE_PROJECT_DELETE : + case LifecycleEvent.PRE_PROJECT_MOVE : + case LifecycleEvent.PRE_PROJECT_OPEN : + flushEnablements((IProject) event.resource); + } + } + + /** + * Configures the nature with the given ID for the given project. + */ + protected void configureNature(final Project project, final String natureID, final MultiStatus errors) { + ISafeRunnable code = new ISafeRunnable() { + @Override + public void run() throws Exception { + IProjectNature nature = createNature(project, natureID); + nature.configure(); + ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, true); + info.setNature(natureID, nature); + } + + @Override + public void handleException(Throwable exception) { + if (exception instanceof CoreException) + errors.add(((CoreException) exception).getStatus()); + else + errors.add(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, project.getFullPath(), NLS.bind(Messages.resources_errorNature, natureID), exception)); + } + }; + if (Policy.DEBUG_NATURES) { + Policy.debug("Configuring nature: " + natureID + " on project: " + project.getName()); //$NON-NLS-1$ //$NON-NLS-2$ + } + SafeRunner.run(code); + } + + /** + * Configures the natures for the given project. Natures found in the new description + * that weren't present in the old description are added, and natures missing from the + * new description are removed. Updates the old description so that it reflects + * the new set of the natures. Errors are added to the given multi-status. + */ + @SuppressWarnings({"unchecked"}) + public void configureNatures(Project project, ProjectDescription oldDescription, ProjectDescription newDescription, MultiStatus status) { + // Be careful not to rely on much state because (de)configuring a nature + // may well result in recursive calls to this method. + HashSet oldNatures = new HashSet(Arrays.asList(oldDescription.getNatureIds(false))); + HashSet newNatures = new HashSet(Arrays.asList(newDescription.getNatureIds(false))); + if (oldNatures.equals(newNatures)) + return; + HashSet deletions = (HashSet) oldNatures.clone(); + HashSet additions = (HashSet) newNatures.clone(); + additions.removeAll(oldNatures); + deletions.removeAll(newNatures); + //do validation of the changes. If any single change is invalid, fail the whole operation + IStatus result = validateAdditions(newNatures, additions, project); + if (!result.isOK()) { + status.merge(result); + return; + } + result = validateRemovals(newNatures, deletions); + if (!result.isOK()) { + status.merge(result); + return; + } + // set the list of nature ids BEFORE (de)configuration so recursive calls will + // not try to do the same work. + oldDescription.setNatureIds(newDescription.getNatureIds(true)); + flushEnablements(project); + //(de)configure in topological order to maintain consistency of configured set + String[] ordered = null; + if (deletions.size() > 0) { + ordered = sortNatureSet(deletions.toArray(new String[deletions.size()])); + for (int i = ordered.length; --i >= 0;) + deconfigureNature(project, ordered[i], status); + } + if (additions.size() > 0) { + ordered = sortNatureSet(additions.toArray(new String[additions.size()])); + for (int i = 0; i < ordered.length; i++) + configureNature(project, ordered[i], status); + } + } + + /** + * Finds the nature extension, and initializes and returns an instance. + */ + protected IProjectNature createNature(Project project, String natureID) throws CoreException { + IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_NATURES, natureID); + if (extension == null) { + String message = NLS.bind(Messages.resources_natureExtension, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null); + } + IConfigurationElement[] configs = extension.getConfigurationElements(); + if (configs.length < 1) { + String message = NLS.bind(Messages.resources_natureClass, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null); + } + //find the runtime configuration element + IConfigurationElement config = null; + for (int i = 0; config == null && i < configs.length; i++) + if ("runtime".equalsIgnoreCase(configs[i].getName())) //$NON-NLS-1$ + config = configs[i]; + if (config == null) { + String message = NLS.bind(Messages.resources_natureFormat, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null); + } + try { + IProjectNature nature = (IProjectNature) config.createExecutableExtension("run"); //$NON-NLS-1$ + nature.setProject(project); + return nature; + } catch (ClassCastException e) { + String message = NLS.bind(Messages.resources_natureImplement, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, e); + } + } + + /** + * Deconfigures the nature with the given ID for the given project. + */ + protected void deconfigureNature(final Project project, final String natureID, final MultiStatus status) { + final ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, true); + IProjectNature existingNature = info.getNature(natureID); + if (existingNature == null) { + // if there isn't a nature then create one so we can deconfig it. + try { + existingNature = createNature(project, natureID); + } catch (CoreException e) { + // Ignore - we are removing a nature that no longer exists in the install + return; + } + } + final IProjectNature nature = existingNature; + ISafeRunnable code = new ISafeRunnable() { + @Override + public void run() throws Exception { + nature.deconfigure(); + info.setNature(natureID, null); + } + + @Override + public void handleException(Throwable exception) { + if (exception instanceof CoreException) + status.add(((CoreException) exception).getStatus()); + else + status.add(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, project.getFullPath(), NLS.bind(Messages.resources_natureDeconfig, natureID), exception)); + } + }; + if (Policy.DEBUG_NATURES) { + Policy.debug("Deconfiguring nature: " + natureID + " on project: " + project.getName()); //$NON-NLS-1$ //$NON-NLS-2$ + } + SafeRunner.run(code); + } + + /** + * Marks all nature descriptors that are involved in cycles + */ + private void detectCycles() { + Collection values = descriptors.values(); + ProjectNatureDescriptor[] natures = values.toArray(new ProjectNatureDescriptor[values.size()]); + for (int i = 0; i < natures.length; i++) + if (natures[i].colour == WHITE) + hasCycles(natures[i]); + } + + /** + * Returns a status indicating failure to configure natures. + */ + protected IStatus failure(String reason) { + return new ResourceStatus(IResourceStatus.INVALID_NATURE_SET, reason); + } + + /** + * Returns the ID of the project nature that claims ownership of the + * builder with the given ID. Returns null if no nature owns that builder. + */ + public synchronized String findNatureForBuilder(String builderID) { + if (buildersToNatures == null) { + buildersToNatures = new HashMap(10); + IProjectNatureDescriptor[] descs = getNatureDescriptors(); + for (int i = 0; i < descs.length; i++) { + String natureId = descs[i].getNatureId(); + String[] builders = ((ProjectNatureDescriptor) descs[i]).getBuilderIds(); + for (int j = 0; j < builders.length; j++) { + //FIXME: how to handle multiple natures specifying same builder + buildersToNatures.put(builders[j], natureId); + } + } + } + return buildersToNatures.get(builderID); + } + + private synchronized void flushEnablements(IProject project) { + natureEnablements.remove(project); + } + + /** + * Returns the cached array of enabled natures for this project, + * or null if there is nothing in the cache. + */ + protected synchronized String[] getEnabledNatures(Project project) { + String[] enabled = natureEnablements.get(project); + if (enabled != null) + return enabled; + enabled = computeNatureEnablements(project); + natureEnablements.put(project, enabled); + return enabled; + } + + /** + * Returns true if there are cycles in the graph of nature + * dependencies starting at root i. Returns false otherwise. + * Marks all descriptors that are involved in the cycle as invalid. + */ + protected boolean hasCycles(ProjectNatureDescriptor desc) { + if (desc.colour == BLACK) { + //this subgraph has already been traversed, so we know the answer + return desc.hasCycle; + } + //if we are already grey, then we have found a cycle + if (desc.colour == GREY) { + desc.hasCycle = true; + desc.colour = BLACK; + return true; + } + //colour current descriptor GREY to indicate it is being visited + desc.colour = GREY; + + //visit all dependents of nature i + String[] required = desc.getRequiredNatureIds(); + for (int i = 0; i < required.length; i++) { + ProjectNatureDescriptor dependency = (ProjectNatureDescriptor) getNatureDescriptor(required[i]); + //missing dependencies cannot create cycles + if (dependency != null && hasCycles(dependency)) { + desc.hasCycle = true; + desc.colour = BLACK; + return true; + } + } + desc.hasCycle = false; + desc.colour = BLACK; + return false; + } + + /** + * Returns true if the given project has linked resources, and false otherwise. + */ + protected boolean hasLinks(IProject project) { + try { + IResource[] children = project.members(); + for (int i = 0; i < children.length; i++) + if (children[i].isLinked()) + return true; + } catch (CoreException e) { + //not possible for project to be inaccessible + Policy.log(e.getStatus()); + } + return false; + } + + /** + * Checks if the two natures have overlapping "one-of-nature" set + * memberships. Returns the name of one such overlap, or null if + * there is no set overlap. + */ + protected String hasSetOverlap(IProjectNatureDescriptor one, IProjectNatureDescriptor two) { + if (one == null || two == null) { + return null; + } + //efficiency not so important because these sets are very small + String[] setsOne = one.getNatureSetIds(); + String[] setsTwo = two.getNatureSetIds(); + for (int iOne = 0; iOne < setsOne.length; iOne++) { + for (int iTwo = 0; iTwo < setsTwo.length; iTwo++) { + if (setsOne[iOne].equals(setsTwo[iTwo])) { + return setsOne[iOne]; + } + } + } + return null; + } + + /** + * Perform depth-first insertion of the given nature ID into the result list. + */ + protected void insert(ArrayList list, Set seen, String id) { + if (seen.contains(id)) + return; + seen.add(id); + //insert prerequisite natures + IProjectNatureDescriptor desc = getNatureDescriptor(id); + if (desc != null) { + String[] prereqs = desc.getRequiredNatureIds(); + for (int i = 0; i < prereqs.length; i++) + insert(list, seen, prereqs[i]); + } + list.add(id); + } + + /* (non-Javadoc) + * Returns true if the given nature is enabled for the given project. + * + * @see IProject#isNatureEnabled(String) + */ + public boolean isNatureEnabled(Project project, String id) { + String[] enabled = getEnabledNatures(project); + for (int i = 0; i < enabled.length; i++) { + if (enabled[i].equals(id)) + return true; + } + return false; + } + + /** + * Only initialize the descriptor cache when we know it is actually needed. + * Running programs may never need to refer to this cache. + */ + private void lazyInitialize() { + if (descriptors != null) + return; + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_NATURES); + IExtension[] extensions = point.getExtensions(); + descriptors = new HashMap(extensions.length * 2 + 1); + for (int i = 0, imax = extensions.length; i < imax; i++) { + IProjectNatureDescriptor desc = null; + try { + desc = new ProjectNatureDescriptor(extensions[i]); + } catch (CoreException e) { + Policy.log(e.getStatus()); + } + if (desc != null) + descriptors.put(desc.getNatureId(), desc); + } + //do cycle detection now so it only has to be done once + //cycle detection on a graph subset is a pain + detectCycles(); + } + + @Override + public void shutdown(IProgressMonitor monitor) { + // do nothing + } + + /* (non-Javadoc) + * @see IWorkspace#sortNatureSet(String[]) + */ + public String[] sortNatureSet(String[] natureIds) { + int count = natureIds.length; + if (count == 0) + return natureIds; + ArrayList result = new ArrayList(count); + HashSet seen = new HashSet(count);//for cycle and duplicate detection + for (int i = 0; i < count; i++) + insert(result, seen, natureIds[i]); + //remove added prerequisites that didn't exist in original list + seen.clear(); + seen.addAll(Arrays.asList(natureIds)); + for (Iterator it = result.iterator(); it.hasNext();) { + Object id = it.next(); + if (!seen.contains(id)) + it.remove(); + } + return result.toArray(new String[result.size()]); + } + + @Override + public void startup(IProgressMonitor monitor) { + ((Workspace) ResourcesPlugin.getWorkspace()).addLifecycleListener(this); + } + + /** + * Validates the given nature additions in the nature set for this + * project. Tolerates existing inconsistencies in the nature set. + * @param newNatures the complete new set of nature IDs for the project, + * including additions + * @param additions the subset of newNatures that represents natures + * being added + * @return An OK status if all additions are valid, and an error status + * if any of the additions introduce new inconsistencies. + */ + protected IStatus validateAdditions(HashSet newNatures, HashSet additions, IProject project) { + Boolean hasLinks = null;//three states: true, false, null (not yet computed) + //perform checks in order from least expensive to most expensive + for (Iterator added = additions.iterator(); added.hasNext();) { + String id = added.next(); + // check for adding a nature that is not available. + IProjectNatureDescriptor desc = getNatureDescriptor(id); + if (desc == null) { + return failure(NLS.bind(Messages.natures_missingNature, id)); + } + // check for adding a nature that creates a circular dependency + if (((ProjectNatureDescriptor) desc).hasCycle) { + return failure(NLS.bind(Messages.natures_hasCycle, id)); + } + // check for adding a nature that has a missing prerequisite. + String[] required = desc.getRequiredNatureIds(); + for (int i = 0; i < required.length; i++) { + if (!newNatures.contains(required[i])) { + return failure(NLS.bind(Messages.natures_missingPrerequisite, id, required[i])); + } + } + // check for adding a nature that creates a duplicated set member. + for (Iterator all = newNatures.iterator(); all.hasNext();) { + String current = all.next(); + if (!current.equals(id)) { + String overlap = hasSetOverlap(desc, getNatureDescriptor(current)); + if (overlap != null) { + return failure(NLS.bind(Messages.natures_multipleSetMembers, overlap)); + } + } + } + //check for adding a nature that has linked resource veto + if (!desc.isLinkingAllowed()) { + if (hasLinks == null) { + hasLinks = hasLinks(project) ? Boolean.TRUE : Boolean.FALSE; + } + if (hasLinks.booleanValue()) + return failure(NLS.bind(Messages.links_vetoNature, project.getName(), id)); + } + } + return Status.OK_STATUS; + } + + /** + * Validates whether a project with the given set of natures should allow + * linked resources. Returns an OK status if linking is allowed, + * otherwise a non-OK status indicating why linking is not allowed. + * Linking is allowed if there is no project nature that explicitly disallows it. + * No validation is done on the nature ids themselves (ids that don't have + * a corresponding nature definition will be ignored). + */ + public IStatus validateLinkCreation(String[] natureIds) { + for (int i = 0; i < natureIds.length; i++) { + IProjectNatureDescriptor desc = getNatureDescriptor(natureIds[i]); + if (desc != null && !desc.isLinkingAllowed()) { + String msg = NLS.bind(Messages.links_natureVeto, desc.getLabel()); + return new ResourceStatus(IResourceStatus.LINKING_NOT_ALLOWED, msg); + } + } + return Status.OK_STATUS; + } + + /** + * Validates the given nature removals in the nature set for this + * project. Tolerates existing inconsistencies in the nature set. + * + * @param newNatures the complete new set of nature IDs for the project, + * excluding deletions + * @param deletions the nature IDs that are being removed from the set. + * @return An OK status if all removals are valid, and a not OK status + * if any of the deletions introduce new inconsistencies. + */ + protected IStatus validateRemovals(HashSet newNatures, HashSet deletions) { + //iterate over new nature set, and ensure that none of their prerequisites are being deleted + for (Iterator it = newNatures.iterator(); it.hasNext();) { + String currentID = it.next(); + IProjectNatureDescriptor desc = getNatureDescriptor(currentID); + if (desc != null) { + String[] required = desc.getRequiredNatureIds(); + for (int i = 0; i < required.length; i++) { + if (deletions.contains(required[i])) { + return failure(NLS.bind(Messages.natures_invalidRemoval, required[i], currentID)); + } + } + } + } + return Status.OK_STATUS; + } + + /* (non-Javadoc) + * @see IWorkspace#validateNatureSet(String[]) + */ + public IStatus validateNatureSet(String[] natureIds) { + int count = natureIds.length; + if (count == 0) + return Status.OK_STATUS; + String msg = Messages.natures_invalidSet; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_NATURE_SET, msg, null); + + //set of the nature ids being validated (String (id)) + HashSet natures = new HashSet(count * 2); + //set of nature sets for which a member nature has been found (String (id)) + HashSet sets = new HashSet(count); + for (int i = 0; i < count; i++) { + String id = natureIds[i]; + ProjectNatureDescriptor desc = (ProjectNatureDescriptor) getNatureDescriptor(id); + if (desc == null) { + result.add(failure(NLS.bind(Messages.natures_missingNature, id))); + continue; + } + if (desc.hasCycle) + result.add(failure(NLS.bind(Messages.natures_hasCycle, id))); + if (!natures.add(id)) + result.add(failure(NLS.bind(Messages.natures_duplicateNature, id))); + //validate nature set one-of constraint + String[] setIds = desc.getNatureSetIds(); + for (int j = 0; j < setIds.length; j++) { + if (!sets.add(setIds[j])) + result.add(failure(NLS.bind(Messages.natures_multipleSetMembers, setIds[j]))); + } + } + //now walk over the set and ensure all pre-requisite natures are present + for (int i = 0; i < count; i++) { + IProjectNatureDescriptor desc = getNatureDescriptor(natureIds[i]); + if (desc == null) + continue; + String[] required = desc.getRequiredNatureIds(); + for (int j = 0; j < required.length; j++) + if (!natures.contains(required[j])) + result.add(failure(NLS.bind(Messages.natures_missingPrerequisite, natureIds[i], required[j]))); + } + //if there are no problems we must return a status whose code is OK + return result.isOK() ? Status.OK_STATUS : (IStatus) result; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java new file mode 100644 index 0000000000..49a2c7857e --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Arrays; +import org.eclipse.core.runtime.Platform; + +/** + * Captures platform specific attributes relevant to the core resources plugin. This + * class is not intended to be instantiated. + */ +public abstract class OS { + private static final String INSTALLED_PLATFORM; + + public static final char[] INVALID_RESOURCE_CHARACTERS; + private static final String[] INVALID_RESOURCE_BASENAMES; + private static final String[] INVALID_RESOURCE_FULLNAMES; + + static { + //find out the OS being used + //setup the invalid names + INSTALLED_PLATFORM = Platform.getOS(); + if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) { + //valid names and characters taken from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp + INVALID_RESOURCE_CHARACTERS = new char[] {'\\', '/', ':', '*', '?', '"', '<', '>', '|'}; + INVALID_RESOURCE_BASENAMES = new String[] {"aux", "com1", "com2", "com3", "com4", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "com5", "com6", "com7", "com8", "com9", "con", "lpt1", "lpt2", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ + "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "nul", "prn"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ + Arrays.sort(INVALID_RESOURCE_BASENAMES); + //CLOCK$ may be used if an extension is provided + INVALID_RESOURCE_FULLNAMES = new String[] {"clock$"}; //$NON-NLS-1$ + } else { + //only front slash and null char are invalid on UNIXes + //taken from http://www.faqs.org/faqs/unix-faq/faq/part2/section-2.html + INVALID_RESOURCE_CHARACTERS = new char[] {'/', '\0',}; + INVALID_RESOURCE_BASENAMES = null; + INVALID_RESOURCE_FULLNAMES = null; + } + } + + /** + * Returns true if the given name is a valid resource name on this operating system, + * and false otherwise. + */ + public static boolean isNameValid(String name) { + //. and .. have special meaning on all platforms + if (name.equals(".") || name.equals("..")) //$NON-NLS-1$ //$NON-NLS-2$ + return false; + if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) { + //empty names are not valid + final int length = name.length(); + if (length == 0) + return false; + final char lastChar = name.charAt(length-1); + // filenames ending in dot are not valid + if (lastChar == '.') + return false; + // file names ending with whitespace are truncated (bug 118997) + if (Character.isWhitespace(lastChar)) + return false; + int dot = name.indexOf('.'); + //on windows, filename suffixes are not relevant to name validity + String basename = dot == -1 ? name : name.substring(0, dot); + if (Arrays.binarySearch(INVALID_RESOURCE_BASENAMES, basename.toLowerCase()) >= 0) + return false; + return Arrays.binarySearch(INVALID_RESOURCE_FULLNAMES, name.toLowerCase()) < 0; + } + return true; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java new file mode 100644 index 0000000000..65d0256a89 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java @@ -0,0 +1,394 @@ +/******************************************************************************* + * 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.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.PathVariableChangeEvent; +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; + +/** + * Core's implementation of IPathVariableManager. + */ +public class PathVariableManager implements IPathVariableManager, IManager { + + static final String VARIABLE_PREFIX = "pathvariable."; //$NON-NLS-1$ + private Set listeners; + private Map> projectListeners; + + private Preferences preferences; + + /** + * Constructor for the class. + */ + public PathVariableManager() { + this.listeners = Collections.synchronizedSet(new HashSet()); + this.projectListeners = Collections.synchronizedMap(new HashMap>()); + this.preferences = ResourcesPlugin.getPlugin().getPluginPreferences(); + } + + /** + * @see org.eclipse.core.resources. + * IPathVariableManager#addChangeListener(IPathVariableChangeListener) + */ + @Override + public void addChangeListener(IPathVariableChangeListener listener) { + listeners.add(listener); + } + + synchronized public void addChangeListener(IPathVariableChangeListener listener, IProject project) { + Collection list = projectListeners.get(project); + if (list == null) { + list = Collections.synchronizedSet(new HashSet()); + projectListeners.put(project, list); + } + list.add(listener); + } + + /** + * Throws a runtime exception if the given name is not valid as a path + * variable name. + */ + private void checkIsValidName(String name) throws CoreException { + IStatus status = validateName(name); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * Throws an exception if the given path is not valid as a path variable + * value. + */ + private void checkIsValidValue(IPath newValue) throws CoreException { + IStatus status = validateValue(newValue); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * Fires a property change event corresponding to a change to the + * current value of the variable with the given name. + * + * @param name the name of the variable, to be used as the variable + * in the event object + * @param value the current value of the path variable or null if + * the variable was deleted + * @param type one of IPathVariableChangeEvent.VARIABLE_CREATED, + * IPathVariableChangeEvent.VARIABLE_CHANGED, or + * IPathVariableChangeEvent.VARIABLE_DELETED + * @see IPathVariableChangeEvent + * @see IPathVariableChangeEvent#VARIABLE_CREATED + * @see IPathVariableChangeEvent#VARIABLE_CHANGED + * @see IPathVariableChangeEvent#VARIABLE_DELETED + */ + private void fireVariableChangeEvent(String name, IPath value, int type) { + fireVariableChangeEvent(this.listeners, name, value, type); + } + + private void fireVariableChangeEvent(Collection list, String name, IPath value, int type) { + if (list.size() == 0) + return; + // use a separate collection to avoid interference of simultaneous additions/removals + Object[] listenerArray = list.toArray(); + final PathVariableChangeEvent pve = new PathVariableChangeEvent(this, name, value, type); + for (int i = 0; i < listenerArray.length; ++i) { + final IPathVariableChangeListener l = (IPathVariableChangeListener) listenerArray[i]; + ISafeRunnable job = new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + // already being logged in SafeRunner#run() + } + + @Override + public void run() throws Exception { + l.pathVariableChanged(pve); + } + }; + SafeRunner.run(job); + } + } + + public void fireVariableChangeEvent(IProject project, String name, IPath value, int type) { + Collection list = projectListeners.get(project); + if (list != null) + fireVariableChangeEvent(list, name, value, type); + } + + /** + * Return a key to use in the Preferences. + */ + private String getKeyForName(String varName) { + return VARIABLE_PREFIX + varName; + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#getPathVariableNames() + */ + @Override + public String[] getPathVariableNames() { + List result = new LinkedList(); + String[] names = preferences.propertyNames(); + for (int i = 0; i < names.length; i++) { + if (names[i].startsWith(VARIABLE_PREFIX)) { + String key = names[i].substring(VARIABLE_PREFIX.length()); + // filter out names for preferences which might be valid in the + // preference store but does not have valid path variable names + // and/or values. We can get in this state if the user has + // edited the file on disk or set a preference using the prefix + // reserved to path variables (#VARIABLE_PREFIX). + // TODO: we may want to look at removing these keys from the + // preference store as a garbage collection means + if (validateName(key).isOK() && validateValue(getValue(key)).isOK()) + result.add(key); + } + } + return result.toArray(new String[result.size()]); + } + + /** + * Note that if a user changes the key in the preferences file to be invalid + * and then calls #getValue using that key, they will get the value back for + * that. But then if they try and call #setValue using the same key it will throw + * an exception. We may want to revisit this behaviour in the future. + * + * @see org.eclipse.core.resources.IPathVariableManager#getValue(String) + */ + @Deprecated + @Override + public IPath getValue(String varName) { + String key = getKeyForName(varName); + String value = preferences.getString(key); + return value.length() == 0 ? null : Path.fromPortableString(value); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#isDefined(String) + */ + @Override + public boolean isDefined(String varName) { + return getValue(varName) != null; + } + + /** + * @see org.eclipse.core.resources. + * IPathVariableManager#removeChangeListener(IPathVariableChangeListener) + */ + @Override + public void removeChangeListener(IPathVariableChangeListener listener) { + listeners.remove(listener); + } + + synchronized public void removeChangeListener(IPathVariableChangeListener listener, IProject project) { + Collection list = projectListeners.get(project); + if (list != null) { + list.remove(listener); + if (list.isEmpty()) + projectListeners.remove(project); + } + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#resolvePath(IPath) + */ + @Deprecated + @Override + public IPath resolvePath(IPath path) { + if (path == null || path.segmentCount() == 0 || path.isAbsolute() || path.getDevice() != null) + return path; + IPath value = getValue(path.segment(0)); + return value == null ? path : value.append(path.removeFirstSegments(1)); + } + + @Override + public URI resolveURI(URI uri) { + if (uri == null || uri.isAbsolute()) + return uri; + if (uri.getSchemeSpecificPart() == null) + return uri; + IPath raw = new Path(uri.getSchemeSpecificPart()); + IPath resolved = resolvePath(raw); + return raw == resolved ? uri : URIUtil.toURI(resolved); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#setValue(String, IPath) + */ + @Override + public void setValue(String varName, IPath newValue) throws CoreException { + checkIsValidName(varName); + //convert path value to canonical form + if (newValue != null && newValue.isAbsolute()) + newValue = FileUtil.canonicalPath(newValue); + checkIsValidValue(newValue); + int eventType; + // read previous value and set new value atomically in order to generate the right event + synchronized (this) { + IPath currentValue = getValue(varName); + boolean variableExists = currentValue != null; + if (!variableExists && newValue == null) + return; + if (variableExists && currentValue.equals(newValue)) + return; + if (newValue == null) { + preferences.setToDefault(getKeyForName(varName)); + eventType = IPathVariableChangeEvent.VARIABLE_DELETED; + } else { + preferences.setValue(getKeyForName(varName), newValue.toPortableString()); + eventType = variableExists ? IPathVariableChangeEvent.VARIABLE_CHANGED : IPathVariableChangeEvent.VARIABLE_CREATED; + } + } + // notify listeners from outside the synchronized block to avoid deadlocks + fireVariableChangeEvent(varName, newValue, eventType); + } + + /** + * @see org.eclipse.core.internal.resources.IManager#shutdown(IProgressMonitor) + */ + @Override + public void shutdown(IProgressMonitor monitor) { + // The preferences for this plug-in are saved in the Plugin.shutdown + // method so we don't have to do it here. + } + + /** + * @see org.eclipse.core.internal.resources.IManager#startup(IProgressMonitor) + */ + @Override + public void startup(IProgressMonitor monitor) { + // since we are accessing the preference store directly, we don't + // need to do any setup here. + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#validateName(String) + */ + @Override + public IStatus validateName(String name) { + String message = null; + if (name.length() == 0) { + message = Messages.pathvar_length; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + char first = name.charAt(0); + if (!Character.isLetter(first) && first != '_') { + message = NLS.bind(Messages.pathvar_beginLetter, String.valueOf(first)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + for (int i = 1; i < name.length(); i++) { + char following = name.charAt(i); + if (Character.isWhitespace(following)) + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, Messages.pathvar_whitespace); + if (!Character.isLetter(following) && !Character.isDigit(following) && following != '_') { + message = NLS.bind(Messages.pathvar_invalidChar, String.valueOf(following)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + } + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#validateValue(IPath) + */ + @Override + public IStatus validateValue(IPath value) { + if (value != null && (!value.isValidPath(value.toString()) || !value.isAbsolute())) { + String message = Messages.pathvar_invalidValue; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#convertToRelative(URI, boolean, String) + */ + @Override + public URI convertToRelative(URI path, boolean force, String variableHint) throws CoreException { + return PathVariableUtil.convertToRelative(this, path, null, false, variableHint); + } + + /** + * see IPathVariableManager#getURIValue(String) + */ + @Override + public URI getURIValue(String name) { + IPath path = getValue(name); + if (path != null) + return URIUtil.toURI(path); + return null; + } + + /** + * see IPathVariableManager#setURIValue(String, URI) + */ + @Override + public void setURIValue(String name, URI value) throws CoreException { + setValue(name, (value != null ? URIUtil.toPath(value) : null)); + } + + /** + * @see IPathVariableManager#validateValue(URI) + */ + @Override + public IStatus validateValue(URI path) { + return validateValue(path != null ? URIUtil.toPath(path) : (IPath) null); + } + + public URI resolveURI(URI uri, IResource resource) { + return resolveURI(uri); + } + + public String[] getPathVariableNames(IResource resource) { + return getPathVariableNames(); + } + + /* + * (non-Javadoc) + * + * @see IPathVariableManager#getVariableRelativePathLocation(IResource, URI) + */ + @Override + public URI getVariableRelativePathLocation(URI location) { + try { + URI result = convertToRelative(location, false, null); + if (!result.equals(location)) + return result; + } catch (CoreException e) { + // handled by returning null + } + return null; + } + + /** + * @see IPathVariableManager#convertToUserEditableFormat(String, boolean) + */ + @Override + public String convertToUserEditableFormat(String value, boolean locationFormat) { + return PathVariableUtil.convertToUserEditableFormatInternal(value, locationFormat); + } + + @Override + public String convertFromUserEditableFormat(String userFormat, boolean locationFormat) { + return PathVariableUtil.convertFromUserEditableFormatInternal(this, userFormat, locationFormat); + } + + @Override + public boolean isUserDefined(String name) { + return ProjectVariableProviderManager.getDefault().findDescriptor(name) == null; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java new file mode 100644 index 0000000000..b176d1cc0f --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java @@ -0,0 +1,506 @@ +/******************************************************************************* + * Copyright (c) 2008, 2012 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 + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.util.ArrayList; +import java.util.LinkedList; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.resources.projectvariables.*; +import org.eclipse.core.resources.IPathVariableManager; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.*; + +public class PathVariableUtil { + + static public String getUniqueVariableName(String variable, IResource resource) { + int index = 1; + variable = getValidVariableName(variable); + String destVariable = variable; + + IPathVariableManager pathVariableManager = resource.getPathVariableManager(); + if (destVariable.startsWith(ParentVariableResolver.NAME) || destVariable.startsWith(ProjectLocationVariableResolver.NAME)) + destVariable = "copy_" + destVariable; //$NON-NLS-1$ + + while (pathVariableManager.isDefined(destVariable)) { + destVariable = destVariable + index; + index++; + } + return destVariable; + } + + public static String getValidVariableName(String variable) { + // remove the argument part if the variable is of the form ${VAR-ARG} + int argumentIndex = variable.indexOf('-'); + if (argumentIndex != -1) + variable = variable.substring(0, argumentIndex); + + variable = variable.trim(); + char first = variable.charAt(0); + if (!Character.isLetter(first) && first != '_') { + variable = 'A' + variable; + } + + StringBuffer builder = new StringBuffer(); + for (int i = 0; i < variable.length(); i++) { + char c = variable.charAt(i); + if ((Character.isLetter(c) || Character.isDigit(c) || c == '_') && + !Character.isWhitespace(c)) + builder.append(c); + } + variable = builder.toString(); + return variable; + } + + public static IPath convertToPathRelativeMacro(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint) throws CoreException { + return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, true, true); + } + + static public IPath convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint) throws CoreException { + return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, true, false); + } + + static public URI convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint) throws CoreException { + return URIUtil.toURI(convertToRelative(pathVariableManager, URIUtil.toPath(originalPath), resource, force, variableHint, true, false)); + } + + static public URI convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro) throws CoreException { + return URIUtil.toURI(convertToRelative(pathVariableManager, URIUtil.toPath(originalPath), resource, force, variableHint)); + } + + static private IPath convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro) throws CoreException { + if (variableHint != null && pathVariableManager.isDefined(variableHint)) { + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variableHint)); + if (value != null) + return wrapInProperFormat(makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variableHint, generateMacro), generateMacro); + } + IPath path = convertToProperCase(originalPath); + IPath newPath = null; + int maxMatchLength = -1; + String[] existingVariables = pathVariableManager.getPathVariableNames(); + for (int i = 0; i < existingVariables.length; i++) { + String variable = existingVariables[i]; + if (skipWorkspace) { + // Variables relative to the workspace are not portable, and defeat the purpose of having linked resource locations, + // so they should not automatically be created relative to the workspace. + if (variable.equals(WorkspaceLocationVariableResolver.NAME)) + continue; + } + if (variable.equals(WorkspaceParentLocationVariableResolver.NAME)) + continue; + if (variable.equals(ParentVariableResolver.NAME)) + continue; + // find closest path to the original path + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); + if (value != null) { + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + if (value.isPrefixOf(path)) { + int matchLength = value.segmentCount(); + if (matchLength > maxMatchLength) { + maxMatchLength = matchLength; + newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); + } + } + } + } + if (newPath != null) + return wrapInProperFormat(newPath, generateMacro); + + if (force) { + int originalSegmentCount = originalPath.segmentCount(); + for (int j = 0; j <= originalSegmentCount; j++) { + IPath matchingPath = path.removeLastSegments(j); + int minDifference = Integer.MAX_VALUE; + for (int k = 0; k < existingVariables.length; k++) { + String variable = existingVariables[k]; + if (skipWorkspace) { + if (variable.equals(WorkspaceLocationVariableResolver.NAME)) + continue; + } + if (variable.equals(WorkspaceParentLocationVariableResolver.NAME)) + continue; + if (variable.equals(ParentVariableResolver.NAME)) + continue; + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); + if (value != null) { + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + if (matchingPath.isPrefixOf(value)) { + int difference = value.segmentCount() - originalSegmentCount; + if (difference < minDifference) { + minDifference = difference; + newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); + } + } + } + } + if (newPath != null) + return wrapInProperFormat(newPath, generateMacro); + } + if (originalSegmentCount == 0) { + String variable = ProjectLocationVariableResolver.NAME; + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + if (originalPath.isPrefixOf(value)) + newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); + if (newPath != null) + return wrapInProperFormat(newPath, generateMacro); + } + } + + if (skipWorkspace) + return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, false, generateMacro); + return originalPath; + } + + private static IPath wrapInProperFormat(IPath newPath, boolean generateMacro) { + if (generateMacro) + newPath = PathVariableUtil.buildVariableMacro(newPath); + return newPath; + } + + private static IPath makeRelativeToVariable(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean generateMacro) { + IPath path = convertToProperCase(originalPath); + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variableHint)); + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + int valueSegmentCount = value.segmentCount(); + if (value.isPrefixOf(path)) { + // transform "c:/foo/bar" into "FOO/bar" + IPath tmp = Path.fromOSString(variableHint); + for (int j = valueSegmentCount;j < originalPath.segmentCount(); j++) { + tmp = tmp.append(originalPath.segment(j)); + } + return tmp; + } + + if (force) { + if (devicesAreCompatible(path, value)) { + // transform "c:/foo/bar/other_child/file.txt" into "${PARENT-1-BAR_CHILD}/other_child/file.txt" + int matchingFirstSegments = path.matchingFirstSegments(value); + if (matchingFirstSegments >= 0) { + String originalName= buildParentPathVariable(variableHint, valueSegmentCount - matchingFirstSegments, true); + IPath tmp = Path.fromOSString(originalName); + for (int j = matchingFirstSegments ;j < originalPath.segmentCount(); j++) { + tmp = tmp.append(originalPath.segment(j)); + } + return tmp; + } + } + } + return originalPath; + } + + private static boolean devicesAreCompatible(IPath path, IPath value) { + return (path.getDevice() != null && value.getDevice() != null) ? + (path.getDevice().equals(value.getDevice())) : + (path.getDevice() == value.getDevice()); + } + + static private IPath convertToProperCase(IPath path) { + if (Platform.getOS().equals(Platform.OS_WIN32)) + return Path.fromPortableString(path.toPortableString().toLowerCase()); + return path; + } + + static public boolean isParentVariable(String variableString) { + return variableString.startsWith(ParentVariableResolver.NAME + '-'); + } + + // the format is PARENT-COUNT-ARGUMENT + static public int getParentVariableCount(String variableString) { + String items[] = variableString.split("-"); //$NON-NLS-1$ + if (items.length == 3) { + try { + Integer count = Integer.valueOf(items[1]); + return count.intValue(); + } catch (NumberFormatException e) { + // nothing + } + } + return -1; + } + + // the format is PARENT-COUNT-ARGUMENT + static public String getParentVariableArgument(String variableString) { + String items[] = variableString.split("-"); //$NON-NLS-1$ + if (items.length == 3) + return items[2]; + return null; + } + + static public String buildParentPathVariable(String variable, int difference, boolean generateMacro) { + String newString = ParentVariableResolver.NAME + "-" + difference + "-" + variable; //$NON-NLS-1$//$NON-NLS-2$ + + if (!generateMacro) + newString = "${" + newString + "}"; //$NON-NLS-1$//$NON-NLS-2$ + return newString; + } + + public static IPath buildVariableMacro(IPath relativeSrcValue) { + String variable = relativeSrcValue.segment(0); + variable = "${" + variable + "}"; //$NON-NLS-1$//$NON-NLS-2$ + return Path.fromOSString(variable).append(relativeSrcValue.removeFirstSegments(1)); + } + + public static String convertFromUserEditableFormatInternal(IPathVariableManager manager, String userFormat, boolean locationFormat) { + char pathPrefix = 0; + if ((userFormat.length() > 0) && (userFormat.charAt(0) == '/' || userFormat.charAt(0) == '\\')) + pathPrefix = userFormat.charAt(0); + String components[] = splitPathComponents(userFormat); + for (int i = 0; i < components.length; i++) { + if (components[i] == null) + continue; + if (isDotDot(components[i])) { + int parentCount = 1; + components[i] = null; + for (int j = i + 1; j < components.length; j++) { + if (components[j] != null) { + if (isDotDot(components[j])) { + parentCount++; + components[j] = null; + } else + break; + } + } + if (i == 0) // this means the value is implicitly relative to the project location + components[0] = PathVariableUtil.buildParentPathVariable(ProjectLocationVariableResolver.NAME, parentCount, false); + else { + for (int j = i - 1; j >= 0; j--) { + if (parentCount == 0) + break; + if (components[j] == null) + continue; + String variable = extractVariable(components[j]); + + boolean hasVariableWithMacroSyntax = true; + if (variable.length() == 0 && (locationFormat && j == 0)) { + variable = components[j]; + hasVariableWithMacroSyntax = false; + } + + try { + if (variable.length() > 0) { + String prefix = new String(); + if (hasVariableWithMacroSyntax) { + int indexOfVariable = components[j].indexOf(variable) - "${".length(); //$NON-NLS-1$ + prefix = components[j].substring(0, indexOfVariable); + String suffix = components[j].substring(indexOfVariable + "${".length() + variable.length() + "}".length()); //$NON-NLS-1$ //$NON-NLS-2$ + if (suffix.length() != 0) { + // Create an intermediate variable, since a syntax of "${VAR}foo/../" + // can't be converted to a "${PARENT-1-VAR}foo" variable. + // So instead, an intermediate variable "VARFOO" will be created of value + // "${VAR}foo", and the string "${PARENT-1-VARFOO}" will be inserted. + String intermediateVariable = PathVariableUtil.getValidVariableName(variable + suffix); + IPath intermediateValue = Path.fromPortableString(components[j]); + int intermediateVariableIndex = 1; + String originalIntermediateVariableName = intermediateVariable; + while (manager.isDefined(intermediateVariable)) { + IPath tmpValue = URIUtil.toPath(manager.getURIValue(intermediateVariable)); + if (tmpValue.equals(intermediateValue)) + break; + intermediateVariable = originalIntermediateVariableName + intermediateVariableIndex; + } + if (!manager.isDefined(intermediateVariable)) + manager.setURIValue(intermediateVariable, URIUtil.toURI(intermediateValue)); + variable = intermediateVariable; + prefix = new String(); + } + } + String newVariable = variable; + if (PathVariableUtil.isParentVariable(variable)) { + String argument = PathVariableUtil.getParentVariableArgument(variable); + int count = PathVariableUtil.getParentVariableCount(variable); + if (argument != null && count != -1) + newVariable = PathVariableUtil.buildParentPathVariable(argument, count + parentCount, locationFormat); + else + newVariable = PathVariableUtil.buildParentPathVariable(variable, parentCount, locationFormat); + } else + newVariable = PathVariableUtil.buildParentPathVariable(variable, parentCount, locationFormat); + components[j] = prefix + newVariable; + break; + } + components[j] = null; + parentCount--; + } catch (CoreException e) { + components[j] = null; + parentCount--; + } + } + } + } + } + StringBuffer buffer = new StringBuffer(); + if (pathPrefix != 0) + buffer.append(pathPrefix); + for (int i = 0; i < components.length; i++) { + if (components[i] != null) { + if (i > 0) + buffer.append(java.io.File.separator); + buffer.append(components[i]); + } + } + return buffer.toString(); + } + + private static boolean isDotDot(String component) { + return component.equals(".."); //$NON-NLS-1$ + } + + private static String[] splitPathComponents(String userFormat) { + ArrayList list = new ArrayList(); + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < userFormat.length(); i++) { + char c = userFormat.charAt(i); + if (c == '/' || c == '\\') { + if (buffer.length() > 0) + list.add(buffer.toString()); + buffer = new StringBuffer(); + } else + buffer.append(c); + } + if (buffer.length() > 0) + list.add(buffer.toString()); + return list.toArray(new String[0]); + } + + public static String convertToUserEditableFormatInternal(String value, boolean locationFormat) { + StringBuffer buffer = new StringBuffer(); + if (locationFormat) { + IPath path = Path.fromOSString(value); + if (path.isAbsolute()) + return path.toOSString(); + int index = value.indexOf(java.io.File.separator); + String variable = index != -1 ? value.substring(0, index): value; + convertVariableToUserFormat(buffer, variable, variable, false); + if (index != -1) + buffer.append(value.substring(index)); + } else { + String components[] = splitVariablesAndContent(value); + for (int i = 0; i < components.length; i++) { + String variable = extractVariable(components[i]); + convertVariableToUserFormat(buffer, components[i], variable, true); + } + } + return buffer.toString(); + } + + private static void convertVariableToUserFormat(StringBuffer buffer, String component, String variable, boolean generateMacro) { + if (PathVariableUtil.isParentVariable(variable)) { + String argument = PathVariableUtil.getParentVariableArgument(variable); + int count = PathVariableUtil.getParentVariableCount(variable); + if (argument != null && count != -1) { + buffer.append(generateMacro? PathVariableUtil.buildVariableMacro(Path.fromOSString(argument)):Path.fromOSString(argument)); + for (int j = 0; j < count; j++) { + buffer.append(java.io.File.separator + ".."); //$NON-NLS-1$ + } + } else + buffer.append(component); + } else + buffer.append(component); + } + /* + * Splits a value (returned by this.getValue(variable) in an array of + * string, where the array is divided between the value content and the + * value variables. + * + * For example, if the value is "${ECLIPSE_HOME}/plugins", the value + * returned will be {"${ECLIPSE_HOME}" "/plugins"} + */ + static String[] splitVariablesAndContent(String value) { + LinkedList result = new LinkedList(); + while (true) { + // we check if the value contains referenced variables with ${VAR} + int index = value.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = getMatchingBrace(value, index); + if (index > 0) + result.add(value.substring(0, index)); + result.add(value.substring(index, endIndex + 1)); + value = value.substring(endIndex + 1); + } else + break; + } + if (value.length() > 0) + result.add(value); + return result.toArray(new String[0]); + } + + /* + * Splits a value (returned by this.getValue(variable) in an array of + * string of the variables contained in the value. + * + * For example, if the value is "${ECLIPSE_HOME}/plugins", the value + * returned will be {"ECLIPSE_HOME"}. If the value is + * "${ECLIPSE_HOME}/${FOO}/plugins", the value returned will be + * {"ECLIPSE_HOME", "FOO"}. + */ + static String[] splitVariableNames(String value) { + LinkedList result = new LinkedList(); + while (true) { + int index = value.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = getMatchingBrace(value, index); + result.add(value.substring(index + 2, endIndex)); + value = value.substring(endIndex + 1); + } else + break; + } + return result.toArray(new String[0]); + } + + /* + * Extracts the variable name from a variable segment. + * + * For example, if the value is "${ECLIPSE_HOME}", the value returned will + * be "ECLIPSE_HOME". If the segment doesn't contain any variable, the value + * returned will be "". + */ + static String extractVariable(String segment) { + int index = segment.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = getMatchingBrace(segment, index); + return segment.substring(index + 2, endIndex); + } + return ""; //$NON-NLS-1$ + } + + // getMatchingBrace("${FOO}/something") returns 5 + // getMatchingBrace("${${OTHER}}/something") returns 10 + // getMatchingBrace("${FOO") returns 5 + static int getMatchingBrace(String value, int index) { + int scope = 0; + for (int i = index + 1; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '}') { + if (scope == 0) + return i; + scope--; + } + if (c == '$') { + if ((i + 1 < value.length()) && (value.charAt(i + 1) == '{')) + scope++; + } + } + return value.length(); + } + + /** + * Returns whether this variable is suited for programmatically determining + * which variable is the most appropriate when creating new linked resources. + * + * @return true if the path variable is preferred. + */ + static public boolean isPreferred(String variableName) { + return !(variableName.equals(WorkspaceLocationVariableResolver.NAME) || + variableName.equals(WorkspaceParentLocationVariableResolver.NAME) || + variableName.equals(ParentVariableResolver.NAME)); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java new file mode 100644 index 0000000000..7841814d97 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * 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 java.io.IOException; +import java.net.*; +import org.eclipse.core.internal.boot.PlatformURLConnection; +import org.eclipse.core.internal.boot.PlatformURLHandler; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.osgi.util.NLS; + +/** + * Platform URL support + * platform:/resource// maps to resource in current workspace + */ +public class PlatformURLResourceConnection extends PlatformURLConnection { + + // resource/ protocol + public static final String RESOURCE = "resource"; //$NON-NLS-1$ + public static final String RESOURCE_URL_STRING = PlatformURLHandler.PROTOCOL + PlatformURLHandler.PROTOCOL_SEPARATOR + '/' + RESOURCE + '/'; + private static URL rootURL; + + public PlatformURLResourceConnection(URL url) { + super(url); + } + + @Override + protected boolean allowCaching() { + return false; // don't cache, workspace is local + } + + @Override + protected URL resolve() throws IOException { + String filePath = url.getFile().trim(); + filePath = URLDecoder.decode(filePath, "UTF-8"); //$NON-NLS-1$ + IPath spec = new Path(filePath).makeRelative(); + if (!spec.segment(0).equals(RESOURCE)) + throw new IOException(NLS.bind(Messages.url_badVariant, url)); + int count = spec.segmentCount(); + // if there is only one segment then we are talking about the workspace root. + if (count == 1) + return rootURL; + // if there are two segments then the second is a project name. + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(spec.segment(1)); + if (!project.exists()) { + String message = NLS.bind(Messages.url_couldNotResolve_projectDoesNotExist, project.getName(), url.toExternalForm()); + throw new IOException(message); + } + + IResource resource = null; + IPath result = null; + + if (count == 2) { + resource = project; + } else { + spec = spec.removeFirstSegments(2); + resource = project.getFile(spec); + } + + result = resource.getLocation(); + + if (result == null) { + URI uri = resource.getLocationURI(); + if (uri != null) { + try { + URL url2 = uri.toURL(); + if (url2.getProtocol().equals("file")) //$NON-NLS-1$ + return url2; + } catch (MalformedURLException e) { + String message = NLS.bind(Messages.url_couldNotResolve_URLProtocolHandlerCanNotResolveURL, uri.toString(), url.toExternalForm()); + throw new IOException(message); + } + } + String message = NLS.bind(Messages.url_couldNotResolve_resourceLocationCanNotBeDetermined, resource.getFullPath(), url.toExternalForm()); + throw new IOException(message); + } + return new URL("file", "", result.toString()); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * This method is called during resource plugin startup() initialization. + * + * @param root URL to the root of the current workspace. + */ + public static void startup(IPath root) { + // register connection type for platform:/resource/ handling + if (rootURL != null) + return; + try { + rootURL = root.toFile().toURL(); + } catch (MalformedURLException e) { + // should never happen but if it does, the resource URL cannot be supported. + return; + } + PlatformURLHandler.register(RESOURCE, PlatformURLResourceConnection.class); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java new file mode 100644 index 0000000000..35519ae047 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * 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 Corporation - initial API and implementation + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.preferences.*; + +/** + * @since 3.1 + */ +public class PreferenceInitializer extends AbstractPreferenceInitializer { + + // internal preference keys + public static final String PREF_OPERATIONS_PER_SNAPSHOT = "snapshots.operations"; //$NON-NLS-1$ + public static final String PREF_DELTA_EXPIRATION = "delta.expiration"; //$NON-NLS-1$ + + // DEFAULTS + public static final boolean PREF_AUTO_REFRESH_DEFAULT = false; + public static final boolean PREF_LIGHTWEIGHT_AUTO_REFRESH_DEFAULT = false; + public static final boolean PREF_DISABLE_LINKING_DEFAULT = false; + public static final String PREF_ENCODING_DEFAULT = ""; //$NON-NLS-1$ + public static final boolean PREF_AUTO_BUILDING_DEFAULT = true; + public static final String PREF_BUILD_ORDER_DEFAULT = ""; //$NON-NLS-1$ + public static final int PREF_MAX_BUILD_ITERATIONS_DEFAULT = 10; + public static final boolean PREF_DEFAULT_BUILD_ORDER_DEFAULT = true; + public final static long PREF_SNAPSHOT_INTERVAL_DEFAULT = 5 * 60 * 1000l; // 5 min + public static final int PREF_OPERATIONS_PER_SNAPSHOT_DEFAULT = 100; + public static final boolean PREF_APPLY_FILE_STATE_POLICY_DEFAULT = true; + public static final long PREF_FILE_STATE_LONGEVITY_DEFAULT = 7 * 24 * 3600 * 1000l; // 7 days + public static final long PREF_MAX_FILE_STATE_SIZE_DEFAULT = 1024 * 1024l; // 1 MB + public static final int PREF_MAX_FILE_STATES_DEFAULT = 50; + public static final long PREF_DELTA_EXPIRATION_DEFAULT = 30 * 24 * 3600 * 1000l; // 30 days + + public PreferenceInitializer() { + super(); + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer#initializeDefaultPreferences() + */ + @Override + public void initializeDefaultPreferences() { + IEclipsePreferences node = DefaultScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES); + // auto-refresh default + node.putBoolean(ResourcesPlugin.PREF_AUTO_REFRESH, PREF_AUTO_REFRESH_DEFAULT); + node.putBoolean(ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, PREF_LIGHTWEIGHT_AUTO_REFRESH_DEFAULT); + + // linked resources default + node.putBoolean(ResourcesPlugin.PREF_DISABLE_LINKING, PREF_DISABLE_LINKING_DEFAULT); + + // build manager defaults + node.putBoolean(ResourcesPlugin.PREF_AUTO_BUILDING, PREF_AUTO_BUILDING_DEFAULT); + node.put(ResourcesPlugin.PREF_BUILD_ORDER, PREF_BUILD_ORDER_DEFAULT); + node.putInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS, PREF_MAX_BUILD_ITERATIONS_DEFAULT); + node.putBoolean(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER, PREF_DEFAULT_BUILD_ORDER_DEFAULT); + + // history store defaults + node.putBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY, PREF_APPLY_FILE_STATE_POLICY_DEFAULT); + node.putLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, PREF_FILE_STATE_LONGEVITY_DEFAULT); + node.putLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, PREF_MAX_FILE_STATE_SIZE_DEFAULT); + node.putInt(ResourcesPlugin.PREF_MAX_FILE_STATES, PREF_MAX_FILE_STATES_DEFAULT); + + // save manager defaults + node.putLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL, PREF_SNAPSHOT_INTERVAL_DEFAULT); + node.putInt(PREF_OPERATIONS_PER_SNAPSHOT, PREF_OPERATIONS_PER_SNAPSHOT_DEFAULT); + node.putLong(PREF_DELTA_EXPIRATION, PREF_DELTA_EXPIRATION_DEFAULT); + + // encoding defaults + node.put(ResourcesPlugin.PREF_ENCODING, PREF_ENCODING_DEFAULT); + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java new file mode 100644 index 0000000000..27b52cbd76 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java @@ -0,0 +1,1371 @@ +/******************************************************************************* + * 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] Project Path Variable Support + * Anton Leherbauer (Wind River) - [198591] Allow Builder to specify scheduling rule + * Francis Lynch (Wind River) - [301563] Save and load tree snapshots + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * Broadcom Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.internal.events.LifecycleEvent; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IMoveDeleteHook; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentTypeMatcher; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.osgi.util.NLS; + +public class Project extends Container implements IProject { + /** + * Option constant (value 2) indicating that the snapshot location shall be + * persisted with the project for autoloading the snapshot when the project + * is imported in another workspace. + *

+ * EXPERIMENTAL. This constant has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. Please do not use this API without + * consulting with the Platform Core team. + *

+ * @see #saveSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public static final int SNAPSHOT_SET_AUTOLOAD = 2; + + protected Project(IPath path, Workspace container) { + super(path, container); + } + + protected void assertCreateRequirements(IProjectDescription description) throws CoreException { + checkDoesNotExist(); + checkDescription(this, description, false); + URI location = description.getLocationURI(); + if (location != null) + return; + //if the project is in the default location, need to check for collision with existing folder of different case + if (!Workspace.caseSensitive) { + IFileStore store = getStore(); + IFileInfo localInfo = store.fetchInfo(); + if (localInfo.exists()) { + 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); + } + } + } + } + + /* + * If the creation boolean is true then this method is being called on project creation. + * Otherwise it is being called via #setDescription. The difference is that we don't allow + * some description fields to change value after project creation. (e.g. project location) + */ + protected MultiStatus basicSetDescription(ProjectDescription description, int updateFlags) { + String message = Messages.resources_projectDesc; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_WRITE_METADATA, message, null); + ProjectDescription current = internalGetDescription(); + current.setComment(description.getComment()); + current.setSnapshotLocationURI(description.getSnapshotLocationURI()); + + // set the build order before setting the references or the natures + current.setBuildSpec(description.getBuildSpec(true)); + + // set the references before the natures + boolean flushOrder = false; + IProject[] oldReferences = current.getReferencedProjects(); + IProject[] newReferences = description.getReferencedProjects(); + if (!Arrays.equals(oldReferences, newReferences)) { + current.setReferencedProjects(newReferences); + flushOrder = true; + } + // Update the dynamic state + flushOrder |= current.updateDynamicState(description); + + if (flushOrder) + workspace.flushBuildOrder(); + + // the natures last as this may cause recursive calls to setDescription. + if ((updateFlags & IResource.AVOID_NATURE_CONFIG) == 0) + workspace.getNatureManager().configureNatures(this, current, description, result); + else + current.setNatureIds(description.getNatureIds(false)); + return result; + } + + @Override + public void build(int trigger, IProgressMonitor monitor) throws CoreException { + if (!isAccessible()) + return; + internalBuild(getActiveBuildConfig(), trigger, null, null, monitor); + } + + @Override + public void build(int trigger, String builderName, Map args, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(builderName); + if (!isAccessible()) + return; + internalBuild(getActiveBuildConfig(), trigger, builderName, args, monitor); + } + + @Override + public void build(IBuildConfiguration config, int trigger, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(config); + // If project isn't accessible, or doesn't contain the build configuration, nothing to do. + if (!isAccessible() || !hasBuildConfig(config.getName())) + return; + internalBuild(config, trigger, null, null, monitor); + } + + /** + * Checks that this resource is accessible. Typically this means that it + * exists. In the case of projects, they must also be open. + * If phantom is true, phantom resources are considered. + * + * @exception CoreException if this resource is not accessible + */ + @Override + public void checkAccessible(int flags) throws CoreException { + super.checkAccessible(flags); + if (!isOpen(flags)) { + String message = NLS.bind(Messages.resources_mustBeOpen, getFullPath()); + throw new ResourceException(IResourceStatus.PROJECT_NOT_OPEN, getFullPath(), message, null); + } + } + + /** + * Checks validity of the given project description. + */ + protected void checkDescription(IProject project, IProjectDescription desc, boolean moving) throws CoreException { + URI location = desc.getLocationURI(); + String message = Messages.resources_invalidProjDesc; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_VALUE, message, null); + status.merge(workspace.validateName(desc.getName(), IResource.PROJECT)); + if (moving) { + // if we got here from a move call then we should check the location in the description since + // its possible that we want to do a rename without moving the contents. (and we shouldn't + // throw an Overlapping mapping exception in this case) So if the source description's location + // is null (we are using the default) or if the locations aren't equal, then validate the location + // of the new description. Otherwise both locations aren't null and they are equal so ignore validation. + URI sourceLocation = internalGetDescription().getLocationURI(); + if (sourceLocation == null || !sourceLocation.equals(location)) + status.merge(workspace.validateProjectLocationURI(project, location)); + } else + // otherwise continue on like before + status.merge(workspace.validateProjectLocationURI(project, location)); + if (!status.isOK()) + throw new ResourceException(status); + } + + @Override + public void close(IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String msg = NLS.bind(Messages.resources_closing_1, getName()); + monitor.beginTask(msg, Policy.totalWork); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, monitor); + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkExists(flags, true); + monitor.subTask(msg); + if (!isOpen(flags)) + return; + // Signal that this resource is about to be closed. Do this at the very + // beginning so that infrastructure pieces have a chance to do clean up + // while the resources still exist. + workspace.beginOperation(true); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, this)); + // flush the build order early in case there is a problem + workspace.flushBuildOrder(); + IProgressMonitor sub = Policy.subMonitorFor(monitor, Policy.opWork / 2, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL); + IStatus saveStatus = workspace.getSaveManager().save(ISaveContext.PROJECT_SAVE, this, sub); + internalClose(); + monitor.worked(Policy.opWork / 2); + if (saveStatus != null && !saveStatus.isOK()) + throw new ResourceException(saveStatus); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void copy(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + // FIXME - the logic here for copying projects needs to be moved to Resource.copy + // so that IResource.copy(IPath,int,IProgressMonitor) works properly for + // projects and honours all update flags + monitor = Policy.monitorFor(monitor); + if (destination.segmentCount() == 1) { + // copy project to project + String projectName = destination.segment(0); + IProjectDescription desc = getDescription(); + desc.setName(projectName); + desc.setLocation(null); + ((ProjectDescription) desc).setSnapshotLocationURI(null); + internalCopy(desc, updateFlags, monitor); + } else { + // will fail since we're trying to copy a project to a non-project + checkCopyRequirements(destination, IResource.PROJECT, updateFlags); + } + } + + @Override + public void copy(IProjectDescription destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + // FIXME - the logic here for copying projects needs to be moved to Resource.copy + // so that IResource.copy(IProjectDescription,int,IProgressMonitor) works properly for + // projects and honours all update flags + Assert.isNotNull(destination); + internalCopy(destination, updateFlags, monitor); + } + + protected void copyMetaArea(IProject source, IProject destination, IProgressMonitor monitor) throws CoreException { + IFileStore oldMetaArea = EFS.getFileSystem(EFS.SCHEME_FILE).getStore(workspace.getMetaArea().locationFor(source)); + IFileStore newMetaArea = EFS.getFileSystem(EFS.SCHEME_FILE).getStore(workspace.getMetaArea().locationFor(destination)); + oldMetaArea.copy(newMetaArea, EFS.NONE, monitor); + } + + @Override + public void create(IProgressMonitor monitor) throws CoreException { + create(null, monitor); + } + + @Override + public void create(IProjectDescription description, IProgressMonitor monitor) throws CoreException { + create(description, IResource.NONE, monitor); + } + + @Override + public void create(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask(Messages.resources_create, Policy.totalWork); + checkValidPath(path, PROJECT, false); + final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); + try { + workspace.prepareOperation(rule, monitor); + if (description == null) { + description = new ProjectDescription(); + description.setName(getName()); + } + assertCreateRequirements(description); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CREATE, this)); + workspace.beginOperation(true); + workspace.createResource(this, updateFlags); + workspace.getMetaArea().create(this); + ProjectInfo info = (ProjectInfo) getResourceInfo(false, true); + + // setup description to obtain project location + ProjectDescription desc = (ProjectDescription) ((ProjectDescription) description).clone(); + desc.setLocationURI(FileUtil.canonicalURI(description.getLocationURI())); + desc.setName(getName()); + internalSetDescription(desc, false); + // see if there potentially are already contents on disk + final boolean hasSavedDescription = getLocalManager().hasSavedDescription(this); + boolean hasContent = hasSavedDescription; + //if there is no project description, there might still be content on disk + if (!hasSavedDescription) + hasContent = getLocalManager().hasSavedContent(this); + try { + // look for a description on disk + if (hasSavedDescription) { + updateDescription(); + //make sure the .location file is written + workspace.getMetaArea().writePrivateDescription(this); + } else { + //write out the project + writeDescription(IResource.FORCE); + } + } catch (CoreException e) { + workspace.deleteResource(this); + throw e; + } + // inaccessible projects have a null modification stamp. + // set this after setting the description as #setDescription + // updates the stamp + info.clearModificationStamp(); + //if a project already had content on disk, mark the project as having unknown children + if (hasContent) + info.set(ICoreConstants.M_CHILDREN_UNKNOWN); + workspace.getSaveManager().requestSnapshot(); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT; + delete(updateFlags, monitor); + } + + @Override + public void delete(boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + delete(updateFlags, monitor); + } + + @Override + public void deleteResource(boolean convertToPhantom, MultiStatus status) throws CoreException { + super.deleteResource(convertToPhantom, status); + // Clear the history store. + clearHistory(null); + // Delete the project metadata. + workspace.getMetaArea().delete(this); + } + + @Override + protected void fixupAfterMoveSource() throws CoreException { + workspace.deleteResource(this); + // check if we deleted a preferences file + ProjectPreferences.deleted(this); + } + + @Override + public IBuildConfiguration getActiveBuildConfig() throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + return internalGetActiveBuildConfig(); + } + + @Override + public IBuildConfiguration getBuildConfig(String configName) throws CoreException { + if (configName == null) + return getActiveBuildConfig(); + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + IBuildConfiguration[] configs = internalGetBuildConfigs(false); + for (int i = 0; i < configs.length; i++) { + if (configs[i].getName().equals(configName)) { + return configs[i]; + } + } + throw new ResourceException(IResourceStatus.BUILD_CONFIGURATION_NOT_FOUND, getFullPath(), null, null); + } + + @Override + public IBuildConfiguration[] getBuildConfigs() throws CoreException { + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + return internalGetBuildConfigs(true); + } + + @Override + public IContentTypeMatcher getContentTypeMatcher() throws CoreException { + return workspace.getContentDescriptionManager().getContentTypeMatcher(this); + } + + @Override + public String getDefaultCharset(boolean checkImplicit) { + // non-existing resources default to parent's charset + if (!exists()) + return checkImplicit ? ResourcesPlugin.getEncoding() : null; + return workspace.getCharsetManager().getCharsetFor(getFullPath(), checkImplicit); + } + + @Override + public IProjectDescription getDescription() throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + ProjectDescription description = ((ProjectInfo) info).getDescription(); + //if the project is currently in the middle of being created, the description might not be available yet + if (description == null) + checkAccessible(NULL_FLAG); + return (IProjectDescription) description.clone(); + } + + @Override + public IProjectNature getNature(String natureID) throws CoreException { + // Has it already been initialized? + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + IProjectNature nature = info.getNature(natureID); + if (nature == null) { + // Not initialized yet. Does this project have the nature? + if (!hasNature(natureID)) + return null; + nature = workspace.getNatureManager().createNature(this, natureID); + info.setNature(natureID, nature); + } + return nature; + } + + @Override + public IContainer getParent() { + return workspace.getRoot(); + } + + @Override + @Deprecated + public IPath getPluginWorkingLocation(IPluginDescriptor plugin) { + if (plugin == null) + return null; + return getWorkingLocation(plugin.getUniqueIdentifier()); + } + + @Override + public IProject getProject() { + return this; + } + + @Override + public IPath getProjectRelativePath() { + return Path.EMPTY; + } + + @Override + public IPath getRawLocation() { + ProjectDescription description = internalGetDescription(); + return description == null ? null : description.getLocation(); + } + + @Override + public URI getRawLocationURI() { + ProjectDescription description = internalGetDescription(); + return description == null ? null : description.getLocationURI(); + } + + @Override + public IBuildConfiguration[] getReferencedBuildConfigs(String configName, boolean includeMissing) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + ProjectDescription description = ((ProjectInfo) info).getDescription(); + //if the project is currently in the middle of being created, the description might not be available yet + if (description == null) + checkAccessible(NULL_FLAG); + if (!hasBuildConfig(configName)) + throw new ResourceException(IResourceStatus.BUILD_CONFIGURATION_NOT_FOUND, getFullPath(), null, null); + return internalGetReferencedBuildConfigs(configName, includeMissing); + } + + @Override + public IProject[] getReferencedProjects() throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + ProjectDescription description = ((ProjectInfo) info).getDescription(); + //if the project is currently in the middle of being created, the description might not be available yet + if (description == null) + checkAccessible(NULL_FLAG); + return description.getAllReferences(true); + } + + @Override + public IProject[] getReferencingProjects() { + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List result = new ArrayList(projects.length); + for (int i = 0; i < projects.length; i++) { + Project project = (Project) projects[i]; + if (!project.isAccessible()) + continue; + ProjectDescription description = project.internalGetDescription(); + if (description == null) + continue; + IProject[] references = description.getAllReferences(false); + for (int j = 0; j < references.length; j++) + if (references[j].equals(this)) { + result.add(projects[i]); + break; + } + } + return result.toArray(new IProject[result.size()]); + } + + @Override + public int getType() { + return PROJECT; + } + + @Override + public IPath getWorkingLocation(String id) { + if (id == null || !exists()) + return null; + IPath result = workspace.getMetaArea().getWorkingLocation(this, id); + result.toFile().mkdirs(); + return result; + } + + @Override + public boolean hasBuildConfig(String configName) throws CoreException { + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + return internalHasBuildConfig(configName); + } + + @Override + public boolean hasNature(String natureID) throws CoreException { + checkAccessible(getFlags(getResourceInfo(false, false))); + // use #internal method to avoid copy but still throw an + // exception if the resource doesn't exist. + IProjectDescription desc = internalGetDescription(); + if (desc == null) + checkAccessible(NULL_FLAG); + return desc.hasNature(natureID); + } + + /** + * Implements all build methods on IProject. + */ + protected void internalBuild(final IBuildConfiguration config, final int trigger, final String builderName, final Map args, IProgressMonitor monitor) throws CoreException { + workspace.run(new IWorkspaceRunnable() { + @Override + public void run(IProgressMonitor innerMonitor) throws CoreException { + innerMonitor = Policy.monitorFor(innerMonitor); + final ISchedulingRule rule = workspace.getRoot(); + try { + innerMonitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + try { + workspace.prepareOperation(rule, innerMonitor); + if (!shouldBuild()) + return; + workspace.beginOperation(true); + workspace.aboutToBuild(Project.this, trigger); + } finally { + workspace.endOperation(rule, false, innerMonitor); + } + final ISchedulingRule buildRule = workspace.getBuildManager().getRule(config, trigger, builderName, args); + try { + IStatus result; + workspace.prepareOperation(buildRule, innerMonitor); + //don't open the tree eagerly because it will be wasted if no build occurs + workspace.beginOperation(false); + result = workspace.getBuildManager().build(config, trigger, builderName, args, Policy.subMonitorFor(innerMonitor, Policy.opWork)); + if (!result.isOK()) + throw new ResourceException(result); + } finally { + workspace.endOperation(buildRule, false, innerMonitor); + try { + workspace.prepareOperation(rule, innerMonitor); + //don't open the tree eagerly because it will be wasted if no change occurs + workspace.beginOperation(false); + workspace.broadcastBuildEvent(Project.this, IResourceChangeEvent.POST_BUILD, trigger); + //building may close the tree, so open it + if (workspace.getElementTree().isImmutable()) + workspace.newWorkingTree(); + } finally { + workspace.endOperation(rule, false, Policy.subMonitorFor(innerMonitor, Policy.endOpWork)); + } + } + } finally { + innerMonitor.done(); + } + } + + /** + * Returns whether this project should be built for a given trigger. + * @return true if the build should proceed, and false otherwise. + */ + private boolean shouldBuild() { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (!exists(flags, true) || !isOpen(flags)) + return false; + return true; + } + + }, null, IWorkspace.AVOID_UPDATE, monitor); + } + + /** + * Closes the project. This is called during restore when there is a failure + * to read the project description. Since it is called during workspace restore, + * it cannot start any operations. + */ + protected void internalClose() throws CoreException { + workspace.flushBuildOrder(); + getMarkerManager().removeMarkers(this, IResource.DEPTH_INFINITE); + // remove each member from the resource tree. + // DO NOT use resource.delete() as this will delete it from disk as well. + IResource[] members = members(IContainer.INCLUDE_PHANTOMS | IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < members.length; i++) { + Resource member = (Resource) members[i]; + workspace.deleteResource(member); + } + // finally mark the project as closed. + ResourceInfo info = getResourceInfo(false, true); + info.clear(M_OPEN); + info.clearSessionProperties(); + info.clearModificationStamp(); + info.setSyncInfo(null); + } + + protected void internalCopy(IProjectDescription destDesc, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_copying, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + String destName = destDesc.getName(); + IPath destPath = new Path(destName).makeAbsolute(); + Project destination = (Project) workspace.getRoot().getProject(destName); + final ISchedulingRule rule = workspace.getRuleFactory().copyRule(this, destination); + try { + workspace.prepareOperation(rule, monitor); + // The following assert method throws CoreExceptions as stated in the IProject.copy API + // and assert for programming errors. See checkCopyRequirements for more information. + assertCopyRequirements(destPath, IResource.PROJECT, updateFlags); + checkDescription(destination, destDesc, false); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_COPY, this, destination, updateFlags)); + + workspace.beginOperation(true); + getLocalManager().refresh(this, DEPTH_INFINITE, true, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + // close the property store so incorrect info is not copied to the destination + getPropertyManager().closePropertyStore(this); + getLocalManager().getHistoryStore().closeHistoryStore(this); + + // copy the meta area for the project + copyMetaArea(this, destination, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + + // copy just the project and not its children yet (tree node, properties) + internalCopyProjectOnly(destination, destDesc, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + + // set the description + destination.internalSetDescription(destDesc, false); + + //create the directory for the new project + destination.getStore().mkdir(EFS.NONE, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + + // call super.copy for each child (excluding project description file) + //make it a best effort copy + message = Messages.resources_copyProblem; + MultiStatus problems = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + + IResource[] children = members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + final int childCount = children.length; + final int childWork = childCount > 1 ? Policy.opWork * 50 / 100 / (childCount - 1) : 0; + for (int i = 0; i < childCount; i++) { + IResource child = children[i]; + if (!isProjectDescriptionFile(child)) { + try { + child.copy(destPath.append(child.getName()), updateFlags, Policy.subMonitorFor(monitor, childWork)); + } catch (CoreException e) { + problems.merge(e.getStatus()); + } + } + } + + // write out the new project description to the meta area + try { + destination.writeDescription(IResource.FORCE); + } catch (CoreException e) { + try { + destination.delete((updateFlags & IResource.FORCE) != 0, null); + } catch (CoreException e2) { + // ignore and rethrow the exception that got us here + } + throw e; + } + monitor.worked(Policy.opWork * 5 / 100); + + // refresh local + monitor.subTask(Messages.resources_updating); + getLocalManager().refresh(destination, DEPTH_INFINITE, true, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + if (!problems.isOK()) + throw new ResourceException(problems); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + /* + * Copies just the project and no children. Does NOT copy the meta area. + */ + protected void internalCopyProjectOnly(IResource destination, IProjectDescription destDesc, IProgressMonitor monitor) throws CoreException { + // close the property store so bogus values aren't copied to the destination + getPropertyManager().closePropertyStore(this); + getLocalManager().getHistoryStore().closeHistoryStore(this); + // copy the tree and properties + workspace.copyTree(this, destination.getFullPath(), IResource.DEPTH_ZERO, IResource.NONE, false); + getPropertyManager().copy(this, destination, IResource.DEPTH_ZERO); + + ProjectInfo info = (ProjectInfo) ((Resource) destination).getResourceInfo(false, true); + + //copy the hidden metadata that we store in the project description + ProjectDescription projectDesc = (ProjectDescription) destDesc; + ProjectDescription internalDesc = ((Project) destination.getProject()).internalGetDescription(); + projectDesc.setLinkDescriptions(internalDesc.getLinks()); + projectDesc.setFilterDescriptions(internalDesc.getFilters()); + projectDesc.setVariableDescriptions(internalDesc.getVariables()); + + //clear properties, markers, and description for the new project, because they shouldn't be copied. + info.description = null; + info.natures = null; + info.setMarkers(null); + info.clearSessionProperties(); + } + + /** + * Like {@link #getActiveBuildConfig()} but doesn't check accessibility. + * Project must be accessible. + * @see #getActiveBuildConfig() + */ + IBuildConfiguration internalGetActiveBuildConfig() { + String configName = internalGetDescription().activeConfiguration; + try { + if (configName != null) + return getBuildConfig(configName); + } catch (CoreException e) { + // Build configuration doesn't exist; treat the first as active. + } + return internalGetBuildConfigs(false)[0]; + } + + /** + * @return IBuildConfiguration[] always contains at least one build configuration + */ + public IBuildConfiguration[] internalGetBuildConfigs(boolean makeCopy) { + ProjectDescription desc = internalGetDescription(); + if (desc == null) + return new IBuildConfiguration[] {new BuildConfiguration(this, IBuildConfiguration.DEFAULT_CONFIG_NAME)}; + return desc.getBuildConfigs(this, makeCopy); + } + + /** + * This is an internal helper method. This implementation is different from the API + * method getDescription(). This one does not check the project accessibility. It exists + * in order to prevent "chicken and egg" problems in places like the project creation. + * It may return null. + */ + public ProjectDescription internalGetDescription() { + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + if (info == null) + return null; + return info.getDescription(); + } + + /** + * Returns the IBuildConfigurations referenced by the passed in build configuration + * @param configName to find references for + * @return IBuildConfiguration[] of referenced configurations; never null. + */ + public IBuildConfiguration[] internalGetReferencedBuildConfigs(String configName, boolean includeMissing) { + ProjectDescription description = internalGetDescription(); + IBuildConfiguration[] refs = description.getAllBuildConfigReferences(configName, false); + Collection configs = new LinkedHashSet(refs.length); + for (int i = 0; i < refs.length; i++) { + try { + configs.add((((BuildConfiguration) refs[i]).getBuildConfig())); + } catch (CoreException e) { + // The project isn't accessible, or the build configuration doesn't exist + // on the project. If requested return the full set of build references which may + // be useful to API consumers + if (includeMissing) + configs.add(refs[i]); + } + } + return configs.toArray(new IBuildConfiguration[configs.size()]); + } + + boolean internalHasBuildConfig(String configName) { + return internalGetDescription().hasBuildConfig(configName); + } + + /** + * Sets this project's description to the given value. This is the body of the + * corresponding API method but is needed separately since it is used + * during workspace restore (i.e., when you cannot do an operation) + */ + void internalSetDescription(IProjectDescription value, boolean incrementContentId) { + // Project has been added / removed. Build order is out-of-step + workspace.flushBuildOrder(); + + ProjectInfo info = (ProjectInfo) getResourceInfo(false, true); + info.setDescription((ProjectDescription) value); + getLocalManager().setLocation(this, info, value.getLocationURI()); + if (incrementContentId) { + info.incrementContentId(); + //if the project is not accessible, stamp will be null and should remain null + if (info.getModificationStamp() != NULL_STAMP) + workspace.updateModificationStamp(info); + } + } + + @Override + public void internalSetLocal(boolean flag, int depth) throws CoreException { + // do nothing for projects, but call for its children + if (depth == IResource.DEPTH_ZERO) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.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++) + ((Resource) children[i]).internalSetLocal(flag, depth); + } + + @Override + public boolean isAccessible() { + return isOpen(); + } + + @Override + public boolean isDerived(int options) { + //projects are never derived + return false; + } + + @Override + public boolean isLinked(int options) { + return false;//projects are never linked + } + + @Override + public boolean isVirtual() { + return false; // projects are never virtual + } + + @Override + public boolean isTeamPrivateMember(int options) { + return false;//projects are never team private members + } + + @Deprecated + @Override + public boolean isLocal(int depth) { + // the flags parameter is ignored for projects so pass anything + return isLocal(-1, depth); + } + + @Deprecated + @Override + public boolean isLocal(int flags, int depth) { + // don't check the flags....projects are always local + 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 boolean isNatureEnabled(String natureId) throws CoreException { + checkAccessible(getFlags(getResourceInfo(false, false))); + return workspace.getNatureManager().isNatureEnabled(this, natureId); + } + + @Override + public boolean isOpen() { + ResourceInfo info = getResourceInfo(false, false); + return isOpen(getFlags(info)); + } + + public boolean isOpen(int flags) { + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_OPEN); + } + + /** + * Returns true if this resource represents the project description file, and + * false otherwise. + */ + protected boolean isProjectDescriptionFile(IResource resource) { + return resource.getType() == IResource.FILE && resource.getFullPath().segmentCount() == 2 && resource.getName().equals(IProjectDescription.DESCRIPTION_FILE_NAME); + } + + @Override + public void loadSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + // load a snapshot of refresh information when project is not opened + if (isOpen()) { + String message = Messages.resources_projectMustNotBeOpen; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, null); + throw new CoreException(status); + } + internalLoadSnapshot(options, snapshotLocation, monitor); + } + + /** + * Loads a snapshot of project meta-data from the given location URI. + * Like {@link IProject#loadSnapshot(int, URI, IProgressMonitor)} but can be + * called when the project is open. + * + * @see IProject#saveSnapshot(int, URI, IProgressMonitor) + */ + private void internalLoadSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + if ((options & SNAPSHOT_TREE) != 0) { + // ensure that path variables are resolved: only ws accessible while project is closed + snapshotLocation = workspace.getPathVariableManager().resolveURI(snapshotLocation); + if (!snapshotLocation.isAbsolute()) { + String message = NLS.bind(Messages.projRead_badSnapshotLocation, snapshotLocation.toString()); + throw new CoreException(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, message, null)); + } + // copy the snapshot from the URI into the project metadata + IPath snapshotPath = workspace.getMetaArea().getRefreshLocationFor(this); + IFileStore snapshotFileStore = EFS.getStore(org.eclipse.core.filesystem.URIUtil.toURI(snapshotPath)); + EFS.getStore(snapshotLocation).copy(snapshotFileStore, EFS.OVERWRITE, monitor); + } + } + + @Override + public void move(IProjectDescription destination, boolean force, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(destination); + move(destination, force ? IResource.FORCE : IResource.NONE, monitor); + } + + @Override + public void move(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(description); + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_moving, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + IProject destination = workspace.getRoot().getProject(description.getName()); + final ISchedulingRule rule = workspace.getRuleFactory().moveRule(this, destination); + try { + workspace.prepareOperation(rule, monitor); + // The following assert method throws CoreExceptions as stated in the IResource.move API + // and assert for programming errors. See checkMoveRequirements for more information. + if (!getName().equals(description.getName())) { + IPath destPath = Path.ROOT.append(description.getName()); + assertMoveRequirements(destPath, IResource.PROJECT, updateFlags); + } + checkDescription(destination, description, true); + workspace.beginOperation(true); + message = Messages.resources_moveProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, null); + WorkManager workManager = workspace.getWorkManager(); + ResourceTree tree = new ResourceTree(getLocalManager(), workManager.getLock(), status, updateFlags); + IMoveDeleteHook hook = workspace.getMoveDeleteHook(); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_MOVE, this, destination, updateFlags)); + int depth = 0; + try { + depth = workManager.beginUnprotected(); + if (!hook.moveProject(tree, this, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2))) + tree.standardMoveProject(this, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2)); + } finally { + workManager.endUnprotected(depth); + } + // Invalidate the tree for further use by clients. + tree.makeInvalid(); + if (!tree.getStatus().isOK()) + throw new ResourceException(tree.getStatus()); + // make sure the move operation is remembered + workspace.getSaveManager().requestSnapshot(); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void open(int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String msg = NLS.bind(Messages.resources_opening_1, getName()); + monitor.beginTask(msg, Policy.totalWork); + monitor.subTask(msg); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, monitor); + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + int flags = getFlags(info); + checkExists(flags, true); + if (isOpen(flags)) + return; + + workspace.beginOperation(true); + // flush the build order early in case there is a problem + workspace.flushBuildOrder(); + info = (ProjectInfo) getResourceInfo(false, true); + info.set(M_OPEN); + //clear the unknown children immediately to avoid background refresh + boolean unknownChildren = info.isSet(M_CHILDREN_UNKNOWN); + if (unknownChildren) + info.clear(M_CHILDREN_UNKNOWN); + // the M_USED flag is used to indicate the difference between opening a project + // for the first time and opening it from a previous close (restoring it from disk) + boolean used = info.isSet(M_USED); + boolean snapshotLoaded = false; + if (!used && !workspace.getMetaArea().getRefreshLocationFor(this).toFile().exists()) { + //auto-load a refresh snapshot if it is set + final boolean hasSavedDescription = getLocalManager().hasSavedDescription(this); + if (hasSavedDescription) { + ProjectDescription updatedDesc = info.getDescription(); + if (updatedDesc != null) { + URI autoloadURI = updatedDesc.getSnapshotLocationURI(); + if (autoloadURI != null) { + try { + autoloadURI = getPathVariableManager().resolveURI(autoloadURI); + internalLoadSnapshot(SNAPSHOT_TREE, autoloadURI, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + snapshotLoaded = true; + } catch (CoreException ce) { + //Log non-existing autoload snapshot as warning only + String msgerr = NLS.bind(Messages.projRead_cannotReadSnapshot, getName(), ce.getLocalizedMessage()); + Policy.log(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, msgerr)); + } + } + } + } + } + boolean minorIssuesDuringRestore = false; + if (used) { + minorIssuesDuringRestore = workspace.getSaveManager().restore(this, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + } else { + info.set(M_USED); + //reconcile any links and groups in the project description + IStatus result = reconcileLinksAndGroups(info.getDescription()); + if (!result.isOK()) + throw new CoreException(result); + workspace.updateModificationStamp(info); + monitor.worked(Policy.opWork * (snapshotLoaded ? 15 : 20) / 100); + } + startup(); + //request a refresh if the project is new and has unknown members on disk + // or restore of the project is not fully successful + if ((!used && unknownChildren) || !minorIssuesDuringRestore) { + boolean refreshed = false; + if (!used) { + refreshed = workspace.getSaveManager().restoreFromRefreshSnapshot(this, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + if (refreshed) { // account for the refresh work + monitor.worked(Policy.opWork * 60 / 100); + } + } + //refresh either in background or foreground + if (!refreshed) { + if ((updateFlags & IResource.BACKGROUND_REFRESH) != 0) { + workspace.refreshManager.refresh(this); + monitor.worked(Policy.opWork * 60 / 100); + } else { + refreshLocal(IResource.DEPTH_INFINITE, Policy.subMonitorFor(monitor, Policy.opWork * 60 / 100)); + } + } + } + //creation of this project may affect overlapping resources + workspace.getAliasManager().updateAliases(this, getStore(), IResource.DEPTH_INFINITE, 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 open(IProgressMonitor monitor) throws CoreException { + open(IResource.NONE, monitor); + } + + /** + * The project description file has changed on disk, resulting in a changed + * set of linked resources. Perform the necessary creations and deletions of + * links to bring the links in sync with those described in the project description. + * @param newDescription the new project description that may have + * changed link descriptions. + * @return status ok if everything went well, otherwise an ERROR multi-status + * describing the problems encountered. + */ + public IStatus reconcileLinksAndGroups(ProjectDescription newDescription) { + String msg = Messages.links_errorLinkReconcile; + HashMap newLinks = newDescription.getLinks(); + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.OPERATION_FAILED, msg, null); + //walk over old linked resources and remove those that are no longer defined + ProjectDescription oldDescription = internalGetDescription(); + if (oldDescription != null) { + HashMap oldLinks = oldDescription.getLinks(); + if (oldLinks != null) { + for (Iterator it = oldLinks.values().iterator(); it.hasNext();) { + LinkDescription oldLink = it.next(); + Resource oldLinkResource = (Resource) findMember(oldLink.getProjectRelativePath()); + if (oldLinkResource == null || !oldLinkResource.isLinked()) + continue; + LinkDescription newLink = null; + if (newLinks != null) + newLink = newLinks.get(oldLink.getProjectRelativePath()); + //if the new link is missing, or has different (raw) location or gender, then remove old link + if (newLink == null || !newLink.getLocationURI().equals(oldLinkResource.getRawLocationURI()) || newLink.getType() != oldLinkResource.getType()) { + try { + oldLinkResource.delete(IResource.NONE, null); + //refresh the resource, because removing a link can reveal a previously hidden resource in parent + oldLinkResource.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } + } + } + } + // walk over new links and groups and create if necessary + // Recreate them in order of the higher up in the tree hierarchy first, + // so we don't have to create intermediate directories that would turn + // out + // to be groups or link folders. + if (newLinks == null) + return status; + //sort links to avoid creating nested links before their parents + TreeSet newLinksAndGroups = new TreeSet(new Comparator() { + @Override + public int compare(LinkDescription arg0, LinkDescription arg1) { + int numberOfSegments0 = arg0.getProjectRelativePath().segmentCount(); + int numberOfSegments1 = arg1.getProjectRelativePath().segmentCount(); + if (numberOfSegments0 != numberOfSegments1) + return numberOfSegments0 - numberOfSegments1; + else if (arg0.equals(arg1)) + return 0; + + return -1; + } + + }); + if (newLinks != null) + newLinksAndGroups.addAll(newLinks.values()); + + for (Iterator it = newLinksAndGroups.iterator(); it.hasNext();) { + Object description = it.next(); + LinkDescription newLink = (LinkDescription) description; + try { + Resource toLink = workspace.newResource(getFullPath().append(newLink.getProjectRelativePath()), newLink.getType()); + IContainer parent = toLink.getParent(); + if (parent != null && !parent.exists() && parent.getType() == FOLDER) + ((Folder) parent).ensureExists(Policy.monitorFor(null)); + if (!toLink.exists() || !toLink.isLinked()) { + if (newLink.isGroup()) + ((Folder) toLink).create(IResource.REPLACE | IResource.VIRTUAL, true, null); + else + toLink.createLink(newLink.getLocationURI(), IResource.REPLACE | IResource.ALLOW_MISSING_LOCAL, null); + } + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } + return status; + } + + @Override + public void saveSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + //Project must be open such that variables can be resolved + checkAccessible(getFlags(getResourceInfo(false, false))); + //URI must not be null and must not refer to undefined path variables + URI resolvedSnapshotLocation = getPathVariableManager().resolveURI(snapshotLocation); + if (resolvedSnapshotLocation == null || !resolvedSnapshotLocation.isAbsolute()) { + String message = NLS.bind(Messages.projRead_badSnapshotLocation, resolvedSnapshotLocation); + throw new CoreException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, message, null)); + } + if ((options & SNAPSHOT_TREE) != 0) { + // write a snapshot of refresh information + try { + IProgressMonitor sub = Policy.subMonitorFor(monitor, Policy.opWork / 2); + workspace.getSaveManager().saveRefreshSnapshot(this, resolvedSnapshotLocation, sub); + } catch (OperationCanceledException e) { + //workspace.getWorkManager().operationCanceled(); + throw e; + } + } + if ((options & SNAPSHOT_SET_AUTOLOAD) != 0) { + IProgressMonitor sub = Policy.subMonitorFor(monitor, Policy.opWork / 2); + //Make absolute URI inside the project relative + if (snapshotLocation != null && snapshotLocation.isAbsolute()) { + snapshotLocation = getPathVariableManager().convertToRelative(snapshotLocation, false, "PROJECT_LOC"); //$NON-NLS-1$ + } + IProjectDescription desc = getDescription(); + ((ProjectDescription) desc).setSnapshotLocationURI(snapshotLocation); + setDescription(desc, IResource.KEEP_HISTORY | IResource.AVOID_NATURE_CONFIG, sub); + } + } finally { + monitor.done(); + } + } + + @Override + public void setDescription(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + // FIXME - update flags should be honored: + // KEEP_HISTORY means capture .project file in local history + // FORCE means overwrite any existing .project file + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask(Messages.resources_setDesc, Policy.totalWork); + ISchedulingRule rule = null; + if ((updateFlags & IResource.AVOID_NATURE_CONFIG) != 0) + rule = workspace.getRuleFactory().modifyRule(this); + else + rule = workspace.getRoot(); + try { + //need to use root rule because nature configuration calls third party code + workspace.prepareOperation(rule, monitor); + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + //if nothing has changed, we don't need to do anything + ProjectDescription oldDescription = internalGetDescription(); + ProjectDescription newDescription = (ProjectDescription) description; + boolean hasPublicChanges = oldDescription.hasPublicChanges(newDescription); + boolean hasPrivateChanges = oldDescription.hasPrivateChanges(newDescription); + if (!hasPublicChanges && !hasPrivateChanges) + return; + checkDescription(this, newDescription, false); + //If we're out of sync and !FORCE, then fail. + //If the file is missing, we want to write the new description then throw an exception. + boolean hadSavedDescription = true; + if (((updateFlags & IResource.FORCE) == 0)) { + hadSavedDescription = getLocalManager().hasSavedDescription(this); + if (hadSavedDescription && !getLocalManager().isDescriptionSynchronized(this)) { + String message = NLS.bind(Messages.resources_projectDescSync, getName()); + throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, getFullPath(), message, null); + } + } + //see if we have an old .prj file + if (!hadSavedDescription) + hadSavedDescription = workspace.getMetaArea().hasSavedProject(this); + workspace.beginOperation(true); + MultiStatus status = basicSetDescription(newDescription, updateFlags); + if (hadSavedDescription && !status.isOK()) + throw new CoreException(status); + //write the new description to the .project file + writeDescription(oldDescription, updateFlags, hasPublicChanges, hasPrivateChanges); + //increment the content id even for private changes + info = getResourceInfo(false, true); + info.incrementContentId(); + workspace.updateModificationStamp(info); + if (!hadSavedDescription) { + String msg = NLS.bind(Messages.resources_missingProjectMetaRepaired, getName()); + status.merge(new ResourceStatus(IResourceStatus.MISSING_DESCRIPTION_REPAIRED, getFullPath(), msg)); + } + if (!status.isOK()) + throw new CoreException(status); + } finally { + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void setDescription(IProjectDescription description, IProgressMonitor monitor) throws CoreException { + // funnel all operations to central method + setDescription(description, IResource.KEEP_HISTORY, monitor); + } + + /** + * Restore the non-persisted state for the project. For example, read and set + * the description from the local meta area. Also, open the property store etc. + * This method is used when an open project is restored and so emulates + * the behaviour of open(). + */ + protected void startup() throws CoreException { + if (!isOpen()) + return; + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_OPEN, this)); + } + + @Override + public void touch(IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_touch, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, monitor); + workspace.beginOperation(true); + super.touch(Policy.subMonitorFor(monitor, Policy.opWork)); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + /** + * The project description file on disk is better than the description in memory. + * Make sure the project description in memory is synchronized with the + * description file contents. + */ + protected void updateDescription() throws CoreException { + if (ProjectDescription.isWriting) + return; + ProjectDescription.isReading = true; + try { + ProjectDescription description = getLocalManager().read(this, false); + //links can only be created if the project is open + IStatus result = null; + if (isOpen()) + result = reconcileLinksAndGroups(description); + internalSetDescription(description, true); + if (result != null && !result.isOK()) + throw new CoreException(result); + } finally { + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); + ProjectDescription.isReading = false; + } + } + + /** + * Writes the project's current description file to disk. + */ + public void writeDescription(int updateFlags) throws CoreException { + writeDescription(internalGetDescription(), updateFlags, true, true); + } + + /** + * Writes the project description file to disk. This is the only method + * that should ever be writing the description, because it ensures that + * the description isn't then immediately discovered as an incoming + * change and read back from disk. + * @param description The description to write + * @param updateFlags The write operation update flags + * @param hasPublicChanges Whether the public sections of the description have changed + * @param hasPrivateChanges Whether the private sections of the description have changed + * @exception CoreException On failure to write the description + */ + public void writeDescription(IProjectDescription description, int updateFlags, boolean hasPublicChanges, boolean hasPrivateChanges) throws CoreException { + if (ProjectDescription.isReading) + return; + ProjectDescription.isWriting = true; + try { + getLocalManager().internalWrite(this, description, updateFlags, hasPublicChanges, hasPrivateChanges); + } finally { + ProjectDescription.isWriting = false; + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java new file mode 100644 index 0000000000..985c5bbba8 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java @@ -0,0 +1,250 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.internal.utils.Cache; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ProjectScope; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy; +import org.eclipse.core.runtime.content.IContentTypeMatcher; +import org.eclipse.core.runtime.preferences.*; +import org.osgi.service.prefs.BackingStoreException; +import org.osgi.service.prefs.Preferences; + +/** + * Manages project-specific content type behavior. + * + * @see ContentDescriptionManager + * @see org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy + * @since 3.1 + */ +public class ProjectContentTypes { + + /** + * A project-aware content type selection policy. + * This class is also a dynamic scope context that will delegate to either + * project or instance scope depending on whether project specific settings were enabled + * for the project in question. + */ + private class ProjectContentTypeSelectionPolicy implements ISelectionPolicy, IScopeContext { + // corresponding project + private Project project; + // cached project scope + private IScopeContext projectScope; + + public ProjectContentTypeSelectionPolicy(Project project) { + this.project = project; + this.projectScope = new ProjectScope(project); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof IScopeContext)) + return false; + IScopeContext other = (IScopeContext) obj; + if (!getName().equals(other.getName())) + return false; + IPath location = getLocation(); + return location == null ? other.getLocation() == null : location.equals(other.getLocation()); + } + + private IScopeContext getDelegate() { + if (!usesContentTypePreferences(project.getName())) + return InstanceScope.INSTANCE; + return projectScope; + } + + @Override + public IPath getLocation() { + return getDelegate().getLocation(); + } + + @Override + public String getName() { + return getDelegate().getName(); + } + + @Override + public IEclipsePreferences getNode(String qualifier) { + return getDelegate().getNode(qualifier); + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public IContentType[] select(IContentType[] candidates, boolean fileName, boolean content) { + return ProjectContentTypes.this.select(project, candidates, fileName, content); + } + } + + private static final String CONTENT_TYPE_PREF_NODE = "content-types"; //$NON-NLS-1$ + + private static final String PREF_LOCAL_CONTENT_TYPE_SETTINGS = "enabled"; //$NON-NLS-1$ + private static final Preferences PROJECT_SCOPE = Platform.getPreferencesService().getRootNode().node(ProjectScope.SCOPE); + private Cache contentTypesPerProject; + private Workspace workspace; + + static boolean usesContentTypePreferences(String projectName) { + try { + // be careful looking up for our node so not to create any nodes as side effect + Preferences node = PROJECT_SCOPE; + //TODO once bug 90500 is fixed, should be simpler + // for now, take the long way + if (!node.nodeExists(projectName)) + return false; + node = node.node(projectName); + if (!node.nodeExists(Platform.PI_RUNTIME)) + return false; + node = node.node(Platform.PI_RUNTIME); + if (!node.nodeExists(CONTENT_TYPE_PREF_NODE)) + return false; + node = node.node(CONTENT_TYPE_PREF_NODE); + return node.getBoolean(PREF_LOCAL_CONTENT_TYPE_SETTINGS, false); + } catch (BackingStoreException e) { + // exception treated when retrieving the project preferences + } + return false; + } + + public ProjectContentTypes(Workspace workspace) { + this.workspace = workspace; + // keep cache small + this.contentTypesPerProject = new Cache(5, 30, 0.4); + } + + /** + * Collect content types associated to the natures configured for the given project. + */ + private Set collectAssociatedContentTypes(Project project) { + String[] enabledNatures = workspace.getNatureManager().getEnabledNatures(project); + if (enabledNatures.length == 0) + return Collections.EMPTY_SET; + Set related = new HashSet(enabledNatures.length); + for (int i = 0; i < enabledNatures.length; i++) { + ProjectNatureDescriptor descriptor = (ProjectNatureDescriptor) workspace.getNatureDescriptor(enabledNatures[i]); + if (descriptor == null) + // no descriptor found for the nature, skip it + continue; + String[] natureContentTypes = descriptor.getContentTypeIds(); + for (int j = 0; j < natureContentTypes.length; j++) + // collect associate content types + related.add(natureContentTypes[j]); + } + return related; + } + + public void contentTypePreferencesChanged(IProject project) { + final ProjectInfo info = (ProjectInfo) ((Project) project).getResourceInfo(false, false); + if (info != null) + info.setMatcher(null); + } + + /** + * Creates a content type matcher for the given project. Takes natures and user settings into account. + */ + private IContentTypeMatcher createMatcher(Project project) { + ProjectContentTypeSelectionPolicy projectContentTypeSelectionPolicy = new ProjectContentTypeSelectionPolicy(project); + return Platform.getContentTypeManager().getMatcher(projectContentTypeSelectionPolicy, projectContentTypeSelectionPolicy); + } + + @SuppressWarnings({"unchecked"}) + private Set getAssociatedContentTypes(Project project) { + final ResourceInfo info = project.getResourceInfo(false, false); + if (info == null) + // the project has been deleted + return null; + final String projectName = project.getName(); + synchronized (contentTypesPerProject) { + Cache.Entry entry = contentTypesPerProject.getEntry(projectName); + if (entry != null) + // we have an entry... + if (entry.getTimestamp() == info.getContentId()) + // ...and it is not stale, so just return it + return (Set) entry.getCached(); + // no cached information found, have to collect associated content types + Set result = collectAssociatedContentTypes(project); + if (entry == null) + // there was no entry before - create one + entry = contentTypesPerProject.addEntry(projectName, result, info.getContentId()); + else { + // just update the existing entry + entry.setTimestamp(info.getContentId()); + entry.setCached(result); + } + return result; + } + } + + public IContentTypeMatcher getMatcherFor(Project project) throws CoreException { + ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, false); + //fail if project has been deleted concurrently + if (info == null) + project.checkAccessible(project.getFlags(info)); + IContentTypeMatcher matcher = info.getMatcher(); + if (matcher != null) + return matcher; + matcher = createMatcher(project); + info.setMatcher(matcher); + return matcher; + } + + /** + * Implements project specific, nature-based selection policy. No content types are vetoed. + * + * The criteria for this policy is as follows: + *
    + *
  1. associated content types should appear before non-associated content types
  2. + *
  3. otherwise, relative ordering should be preserved.
  4. + *
+ * + * @see org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy + */ + final IContentType[] select(Project project, IContentType[] candidates, boolean fileName, boolean content) { + // since no vetoing is done here, don't go further if there is nothing to sort + if (candidates.length < 2) + return candidates; + final Set associated = getAssociatedContentTypes(project); + if (associated == null || associated.isEmpty()) + // project has no content types associated + return candidates; + int associatedCount = 0; + for (int i = 0; i < candidates.length; i++) + // is it an associated content type? + if (associated.contains(candidates[i].getId())) { + // need to move it to the right spot (unless all types visited so far are associated as well) + if (associatedCount < i) { + final IContentType promoted = candidates[i]; + // move all non-associated content types before it one one position up... + for (int j = i; j > associatedCount; j--) + candidates[j] = candidates[j - 1]; + // ...so there is an empty spot for the content type we are promoting + candidates[associatedCount] = promoted; + } + associatedCount++; + } + return candidates; + } +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java new file mode 100644 index 0000000000..f8c23be1f1 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java @@ -0,0 +1,931 @@ +/******************************************************************************* + * 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) - [245937] setLinkLocation() detects non-change + * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * Broadcom Corporation - build configurations and references + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.util.*; +import java.util.Map.Entry; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +public class ProjectDescription extends ModelObject implements IProjectDescription { + // constants + private static final IBuildConfiguration[] EMPTY_BUILD_CONFIG_REFERENCE_ARRAY = new IBuildConfiguration[0]; + private static final ICommand[] EMPTY_COMMAND_ARRAY = new ICommand[0]; + private static final IProject[] EMPTY_PROJECT_ARRAY = new IProject[0]; + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final String EMPTY_STR = ""; //$NON-NLS-1$ + + protected static boolean isReading = false; + + //flags to indicate when we are in the middle of reading or writing a + // workspace description + //these can be static because only one description can be read at once. + protected static boolean isWriting = false; + protected ICommand[] buildSpec = EMPTY_COMMAND_ARRAY; + protected String comment = EMPTY_STR; + + // Build configuration + References state + /** Id of the currently active build configuration */ + protected String activeConfiguration = IBuildConfiguration.DEFAULT_CONFIG_NAME; + /** + * The 'real' build configuration names set on this project. + * This doesn't contain the generated 'default' build configuration with name + * {@link IBuildConfiguration#DEFAULT_CONFIG_NAME} + * when no build configurations have been defined. + */ + protected String[] configNames = EMPTY_STRING_ARRAY; + // Static + Dynamic project level references + protected IProject[] staticRefs = EMPTY_PROJECT_ARRAY; + protected IProject[] dynamicRefs = EMPTY_PROJECT_ARRAY; + /** Map from config name in this project -> build configurations in other projects */ + protected HashMap dynamicConfigRefs = new HashMap(1); + + // Cache of the build configurations + protected volatile IBuildConfiguration[] cachedBuildConfigs; + // Cached build configuration references. Not persisted. + protected Map cachedConfigRefs = Collections.synchronizedMap(new HashMap(1)); + // Cached project level references. + protected volatile IProject[] cachedRefs = null; + + /** + * Map of (IPath -> LinkDescription) pairs for each linked resource + * in this project, where IPath is the project relative path of the resource. + */ + protected HashMap linkDescriptions = null; + + /** + * Map of (IPath -> LinkedList) pairs for each filtered resource + * in this project, where IPath is the project relative path of the resource. + */ + protected HashMap> filterDescriptions = null; + + /** + * Map of (String -> VariableDescription) pairs for each variable in this + * project, where String is the name of the variable. + */ + protected HashMap variableDescriptions = null; + + // fields + protected URI location = null; + protected String[] natures = EMPTY_STRING_ARRAY; + protected URI snapshotLocation = null; + + public ProjectDescription() { + super(); + } + + @Override + @SuppressWarnings({"unchecked"}) + public Object clone() { + ProjectDescription clone = (ProjectDescription) super.clone(); + //don't want the clone to have access to our internal link locations table or builders + clone.linkDescriptions = null; + clone.filterDescriptions = null; + if (variableDescriptions != null) + clone.variableDescriptions = (HashMap) variableDescriptions.clone(); + clone.buildSpec = getBuildSpec(true); + clone.dynamicConfigRefs = (HashMap) dynamicConfigRefs.clone(); + clone.cachedConfigRefs = Collections.synchronizedMap(new HashMap(1)); + clone.clearCachedReferences(null); + return clone; + } + + /** + * Clear cached references for the specified build config name + * or all if configName is null. + */ + private void clearCachedReferences(String configName) { + if (configName == null) + cachedConfigRefs.clear(); + else + cachedConfigRefs.remove(configName); + cachedRefs = null; + } + + /** + * Returns a copy of the given array of build configs with all duplicates removed + */ + private IBuildConfiguration[] copyAndRemoveDuplicates(IBuildConfiguration[] values) { + Set set = new LinkedHashSet(Arrays.asList(values)); + return set.toArray(new IBuildConfiguration[set.size()]); + } + + /** + * Returns a copy of the given array with all duplicates removed + */ + private IProject[] copyAndRemoveDuplicates(IProject[] projects) { + IProject[] result = new IProject[projects.length]; + int count = 0; + next: for (int i = 0; i < projects.length; i++) { + IProject project = projects[i]; + // scan to see if there are any other projects by the same name + for (int j = 0; j < count; j++) + if (project.equals(result[j])) + continue next; + // not found + result[count++] = project; + } + if (count < projects.length) { + //shrink array + IProject[] reduced = new IProject[count]; + System.arraycopy(result, 0, reduced, 0, count); + return reduced; + } + return result; + } + + /** + * Helper to turn an array of projects into an array of {@link IBuildConfiguration} to the + * projects' active configuration + * Order is preserved - the buildConfigs appear for each project in the order + * that the projects were specified. + * @param projects projects to get the active configuration from + * @return collection of build config references + */ + private Collection getBuildConfigReferencesFromProjects(IProject[] projects) { + List refs = new ArrayList(projects.length); + for (int i = 0; i < projects.length; i++) + refs.add(new BuildConfiguration(projects[i], null)); + return refs; + } + + /** + * Helper to fetch projects from an array of build configuration references + * @param refs + * @return List + */ + private Collection getProjectsFromBuildConfigRefs(IBuildConfiguration[] refs) { + LinkedHashSet projects = new LinkedHashSet(refs.length); + for (int i = 0; i < refs.length; i++) + projects.add(refs[i].getProject()); + return projects; + } + + public String getActiveBuildConfig() { + return activeConfiguration; + } + + /** + * Returns the union of the description's static and dynamic project references, + * with duplicates omitted. The calculation is optimized by caching the result + * Call the configuration based implementation. + * @see #getAllBuildConfigReferences(String, boolean) + */ + public IProject[] getAllReferences(boolean makeCopy) { + IProject[] projRefs = cachedRefs; + if (projRefs == null) { + IBuildConfiguration[] refs; + if (hasBuildConfig(activeConfiguration)) + refs = getAllBuildConfigReferences(activeConfiguration, false); + else if (configNames.length > 0) + refs = getAllBuildConfigReferences(configNames[0], false); + else + // No build configuration => fall-back to default + refs = getAllBuildConfigReferences(IBuildConfiguration.DEFAULT_CONFIG_NAME, false); + Collection l = getProjectsFromBuildConfigRefs(refs); + projRefs = cachedRefs = l.toArray(new IProject[l.size()]); + } + //still need to copy the result to prevent tampering with the cache + return makeCopy ? (IProject[]) projRefs.clone() : projRefs; + } + + /** + * The main entrance point to fetch the full set of Project references. + * + * Returns the union of all the description's references. Includes static and dynamic + * project level references as well as build configuration references for the configuration + * with the given id. + * Duplicates are omitted. The calculation is optimized by caching the result. + * Note that these BuildConfiguration references may have null name. They must + * be resolved using {@link BuildConfiguration#getBuildConfig()} before use. + * Returns an empty array if the given configName does not exist in the description. + */ + public IBuildConfiguration[] getAllBuildConfigReferences(String configName, boolean makeCopy) { + if (!hasBuildConfig(configName)) + return EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; + IBuildConfiguration[] refs = cachedConfigRefs.get(configName); + if (refs == null) { + Set references = new LinkedHashSet(); + IBuildConfiguration[] dynamicBuildConfigs = dynamicConfigRefs.containsKey(configName) ? dynamicConfigRefs.get(configName) : EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; + Collection dynamic = getBuildConfigReferencesFromProjects(dynamicRefs); + Collection statik = getBuildConfigReferencesFromProjects(staticRefs); + + // Combine all references: + // New build config references (which only come in dynamic form) trump all others. + references.addAll(Arrays.asList(dynamicBuildConfigs)); + // We preserve the previous order of static project references before dynamic project references + references.addAll(statik); + references.addAll(dynamic); + refs = references.toArray(new IBuildConfiguration[references.size()]); + cachedConfigRefs.put(configName, refs); + } + return makeCopy ? (IBuildConfiguration[]) refs.clone() : refs; + } + + /** + * Used by Project to get the buildConfigs on the description. + * @return the project configurations + */ + public IBuildConfiguration[] getBuildConfigs(IProject project, boolean makeCopy) { + IBuildConfiguration[] configs = cachedBuildConfigs; + // Ensure project is up to date in the cache + if (configs != null && !project.equals(configs[0].getProject())) + configs = null; + if (configs == null) { + if (configNames.length == 0) + configs = new IBuildConfiguration[] {new BuildConfiguration(project)}; + else { + configs = new IBuildConfiguration[configNames.length]; + for (int i = 0; i < configs.length; i++) + configs[i] = new BuildConfiguration(project, configNames[i]); + } + cachedBuildConfigs = configs; + } + return makeCopy ? (IBuildConfiguration[]) configs.clone() : configs; + } + + /* (non-Javadoc) + * @see IProjectDescription#getBuildConfigReferences(String) + */ + @Override + public IBuildConfiguration[] getBuildConfigReferences(String configName) { + return getBuildConfigRefs(configName, true); + } + + public IBuildConfiguration[] getBuildConfigRefs(String configName, boolean makeCopy) { + if (!hasBuildConfig(configName) || !dynamicConfigRefs.containsKey(configName)) + return EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; + + return makeCopy ? (IBuildConfiguration[]) dynamicConfigRefs.get(configName).clone() : dynamicConfigRefs.get(configName); + } + + /** + * Returns the build configuration references map + * @param makeCopy + */ + @SuppressWarnings({"unchecked"}) + public Map getBuildConfigReferences(boolean makeCopy) { + return makeCopy ? (Map) dynamicConfigRefs.clone() : dynamicConfigRefs; + } + + /* (non-Javadoc) + * @see IProjectDescription#getBuildSpec() + */ + @Override + public ICommand[] getBuildSpec() { + return getBuildSpec(true); + } + + public ICommand[] getBuildSpec(boolean makeCopy) { + //thread safety: copy reference in case of concurrent write + ICommand[] oldCommands = this.buildSpec; + if (oldCommands == null) + return EMPTY_COMMAND_ARRAY; + if (!makeCopy) + return oldCommands; + ICommand[] result = new ICommand[oldCommands.length]; + for (int i = 0; i < result.length; i++) + result[i] = (ICommand) ((BuildCommand) oldCommands[i]).clone(); + return result; + } + + /* (non-Javadoc) + * @see IProjectDescription#getComment() + */ + @Override + public String getComment() { + return comment; + } + + /* (non-Javadoc) + * @see IProjectDescription#getDynamicReferences() + */ + @Override + public IProject[] getDynamicReferences() { + return getDynamicReferences(true); + } + + public IProject[] getDynamicReferences(boolean makeCopy) { + return makeCopy ? (IProject[]) dynamicRefs.clone() : dynamicRefs; + } + + /** + * Returns the link location for the given resource name. Returns null if + * no such link exists. + */ + public URI getLinkLocationURI(IPath aPath) { + if (linkDescriptions == null) + return null; + LinkDescription desc = linkDescriptions.get(aPath); + return desc == null ? null : desc.getLocationURI(); + } + + /** + * Returns the filter for the given resource name. Returns null if + * no such filter exists. + */ + synchronized public LinkedList getFilter(IPath aPath) { + if (filterDescriptions == null) + return null; + return filterDescriptions.get(aPath); + } + + /** + * Returns the map of link descriptions (IPath (project relative path) -> LinkDescription). + * Since this method is only used internally, it never creates a copy. + * Returns null if the project does not have any linked resources. + */ + public HashMap getLinks() { + return linkDescriptions; + } + + /** + * Returns the map of filter descriptions (IPath (project relative path) -> LinkedList). + * Since this method is only used internally, it never creates a copy. + * Returns null if the project does not have any filtered resources. + */ + public HashMap> getFilters() { + return filterDescriptions; + } + + /** + * Returns the map of variable descriptions (String (variable name) -> + * VariableDescription). Since this method is only used internally, it never + * creates a copy. Returns null if the project does not have any variables. + */ + public HashMap getVariables() { + return variableDescriptions; + } + + /** + * @see IProjectDescription#getLocation() + * @deprecated + */ + @Override + @Deprecated + public IPath getLocation() { + if (location == null) + return null; + return FileUtil.toPath(location); + } + + /* (non-Javadoc) + * @see IProjectDescription#getLocationURI() + */ + @Override + public URI getLocationURI() { + return location; + } + + /* (non-Javadoc) + * @see IProjectDescription#getNatureIds() + */ + @Override + public String[] getNatureIds() { + return getNatureIds(true); + } + + public String[] getNatureIds(boolean makeCopy) { + if (natures == null) + return EMPTY_STRING_ARRAY; + return makeCopy ? (String[]) natures.clone() : natures; + } + + /* (non-Javadoc) + * @see IProjectDescription#getReferencedProjects() + */ + @Override + public IProject[] getReferencedProjects() { + return getReferencedProjects(true); + } + + public IProject[] getReferencedProjects(boolean makeCopy) { + if (staticRefs == null) + return EMPTY_PROJECT_ARRAY; + return makeCopy ? (IProject[]) staticRefs.clone() : staticRefs; + } + + /** + * Returns the URI to load a resource snapshot from. + * May return null if no snapshot is set. + *

+ * EXPERIMENTAL. This constant has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. Please do not use this API without + * consulting with the Platform Core team. + *

+ * @return the snapshot location URI, + * or null. + * @see IProject#loadSnapshot(int, URI, IProgressMonitor) + * @see #setSnapshotLocationURI(URI) + * @since 3.6 + */ + public URI getSnapshotLocationURI() { + return snapshotLocation; + } + + /* (non-Javadoc) + * @see IProjectDescription#hasNature(String) + */ + @Override + public boolean hasNature(String natureID) { + String[] natureIDs = getNatureIds(false); + for (int i = 0; i < natureIDs.length; ++i) + if (natureIDs[i].equals(natureID)) + return true; + return false; + } + + /** + * Helper method to compare two maps of Configuration Name -> IBuildConfigurationReference[] + * @return boolean indicating if there are differences between the two maps + */ + private static boolean configRefsHaveChanges(Map m1, Map m2) { + if (m1.size() != m2.size()) + return true; + for (Iterator> it = m1.entrySet().iterator(); it.hasNext();) { + Entry e = it.next(); + if (!m2.containsKey(e.getKey())) + return true; + if (!Arrays.equals(e.getValue(), m2.get(e.getKey()))) + return true; + } + return false; + } + + /** + * Internal method to check if the description has a given build configuration. + */ + boolean hasBuildConfig(String buildConfigName) { + Assert.isNotNull(buildConfigName); + if (configNames.length == 0) + return IBuildConfiguration.DEFAULT_CONFIG_NAME.equals(buildConfigName); + for (int i = 0; i < configNames.length; i++) + if (configNames[i].equals(buildConfigName)) + return true; + return false; + } + + /** + * Returns true if any private attributes of the description have changed. + * Private attributes are those that are not stored in the project description + * file (.project). + */ + public boolean hasPrivateChanges(ProjectDescription description) { + if (location == null) { + if (description.location != null) + return true; + } else if (!location.equals(description.location)) + return true; + + if (!Arrays.equals(dynamicRefs, description.dynamicRefs)) + return true; + + // Build Configuration state + if (!activeConfiguration.equals(description.activeConfiguration)) + return true; + if (!Arrays.equals(configNames, description.configNames)) + return true; + // Configuration level references + if (configRefsHaveChanges(dynamicConfigRefs, description.dynamicConfigRefs)) + return true; + + return false; + } + + /** + * Returns true if any public attributes of the description have changed. + * Public attributes are those that are stored in the project description + * file (.project). + */ + public boolean hasPublicChanges(ProjectDescription description) { + if (!getName().equals(description.getName())) + return true; + if (!comment.equals(description.getComment())) + return true; + //don't bother optimizing if the order has changed + if (!Arrays.equals(buildSpec, description.getBuildSpec(false))) + return true; + if (!Arrays.equals(staticRefs, description.getReferencedProjects(false))) + return true; + if (!Arrays.equals(natures, description.getNatureIds(false))) + return true; + + HashMap> otherFilters = description.getFilters(); + if ((filterDescriptions == null) && (otherFilters != null)) + return otherFilters != null; + if ((filterDescriptions != null) && !filterDescriptions.equals(otherFilters)) + return true; + + HashMap otherVariables = description.getVariables(); + if ((variableDescriptions == null) && (otherVariables != null)) + return true; + if ((variableDescriptions != null) && !variableDescriptions.equals(otherVariables)) + return true; + + final HashMap otherLinks = description.getLinks(); + if (linkDescriptions != otherLinks) { + if (linkDescriptions == null || !linkDescriptions.equals(otherLinks)) + return true; + } + + final URI otherSnapshotLoc = description.getSnapshotLocationURI(); + if (snapshotLocation != otherSnapshotLoc) { + if (snapshotLocation == null || !snapshotLocation.equals(otherSnapshotLoc)) + return true; + } + return false; + } + + /* (non-Javadoc) + * @see IProjectDescription#newCommand() + */ + @Override + public ICommand newCommand() { + return new BuildCommand(); + } + + @Override + public void setActiveBuildConfig(String configName) { + Assert.isNotNull(configName); + if (!configName.equals(activeConfiguration)) + clearCachedReferences(null); + activeConfiguration = configName; + } + + /* (non-Javadoc) + * @see IProjectDescription#setBuildSpec(ICommand[]) + */ + @Override + public void setBuildSpec(ICommand[] value) { + Assert.isLegal(value != null); + //perform a deep copy in case clients perform further changes to the command + ICommand[] result = new ICommand[value.length]; + for (int i = 0; i < result.length; i++) { + result[i] = (ICommand) ((BuildCommand) value[i]).clone(); + //copy the reference to any builder instance from the old build spec + //to preserve builder states if possible. + for (int j = 0; j < buildSpec.length; j++) { + if (result[i].equals(buildSpec[j])) { + ((BuildCommand) result[i]).setBuilders(((BuildCommand) buildSpec[j]).getBuilders()); + break; + } + } + } + buildSpec = result; + } + + /* (non-Javadoc) + * @see IProjectDescription#setComment(String) + */ + @Override + public void setComment(String value) { + comment = value; + } + + /* (non-Javadoc) + * @see IProjectDescription#setDynamicReferences(IProject[]) + */ + @Override + public void setDynamicReferences(IProject[] value) { + Assert.isLegal(value != null); + dynamicRefs = copyAndRemoveDuplicates(value); + clearCachedReferences(null); + } + + public void setBuildConfigReferences(HashMap refs) { + dynamicConfigRefs = new HashMap(refs); + clearCachedReferences(null); + } + + /* (non-Javadoc) + * @see IProjectDescription#setDynamicConfigReferences(String, IBuildConfiguration[]) + */ + @Override + public void setBuildConfigReferences(String configName, IBuildConfiguration[] references) { + Assert.isLegal(configName != null); + Assert.isLegal(references != null); + if (!hasBuildConfig(configName)) + return; + dynamicConfigRefs.put(configName, copyAndRemoveDuplicates(references)); + clearCachedReferences(configName); + } + + /* + * (non-Javadoc) + * @see IProjectDescription#setBuildConfigurations(String[]) + */ + @Override + public void setBuildConfigs(String[] names) { + // Remove references for deleted buildConfigs + LinkedHashSet buildConfigNames = new LinkedHashSet(); + + if (names == null || names.length == 0) { + configNames = EMPTY_STRING_ARRAY; + buildConfigNames.add(IBuildConfiguration.DEFAULT_CONFIG_NAME); + } else { + // Filter out duplicates + for (String n : names) { + Assert.isLegal(n != null); + buildConfigNames.add(n); + } + + if (buildConfigNames.size() == 1 && ((buildConfigNames.iterator().next())).equals(IBuildConfiguration.DEFAULT_CONFIG_NAME)) + configNames = EMPTY_STRING_ARRAY; + else + configNames = buildConfigNames.toArray(new String[buildConfigNames.size()]); + } + + // Remove references for deleted buildConfigs + boolean modified = dynamicConfigRefs.keySet().retainAll(buildConfigNames); + if (modified) + clearCachedReferences(null); + // Clear the cached IBuildConfiguration[] + cachedBuildConfigs = null; + } + + /** + * Sets the map of link descriptions (String name -> LinkDescription). + * Since this method is only used internally, it never creates a copy. May + * pass null if this project does not have any linked resources + */ + public void setLinkDescriptions(HashMap linkDescriptions) { + this.linkDescriptions = linkDescriptions; + } + + /** + * Sets the map of filter descriptions (String name -> LinkedList). + * Since this method is only used internally, it never creates a copy. May + * pass null if this project does not have any filtered resources + */ + public void setFilterDescriptions(HashMap> filterDescriptions) { + this.filterDescriptions = filterDescriptions; + } + + /** + * Sets the map of variable descriptions (String name -> + * VariableDescription). Since this method is only used internally, it never + * creates a copy. May pass null if this project does not have any variables + */ + public void setVariableDescriptions(HashMap variableDescriptions) { + this.variableDescriptions = variableDescriptions; + } + + /** + * Sets the description of a link. Setting to a description of null will + * remove the link from the project description. + * @return true if the description was actually changed, + * false otherwise. + * @since 3.5 returns boolean (was void before) + */ + @SuppressWarnings({"unchecked"}) + public boolean setLinkLocation(IPath path, LinkDescription description) { + HashMap tempMap = linkDescriptions; + if (description != null) { + //addition or modification + if (tempMap == null) + tempMap = new HashMap(10); + else + //copy on write to protect against concurrent read + tempMap = (HashMap) tempMap.clone(); + Object oldValue = tempMap.put(path, description); + if (oldValue != null && description.equals(oldValue)) { + //not actually changed anything + return false; + } + linkDescriptions = tempMap; + } else { + //removal + if (tempMap == null) + return false; + //copy on write to protect against concurrent access + HashMap newMap = (HashMap) tempMap.clone(); + Object oldValue = newMap.remove(path); + if (oldValue == null) { + //not actually changed anything + return false; + } + linkDescriptions = newMap.size() == 0 ? null : newMap; + } + return true; + } + + /** + * Add the description of a filter. Setting to a description of null will + * remove the filter from the project description. + */ + synchronized public void addFilter(IPath path, FilterDescription description) { + Assert.isNotNull(description); + if (filterDescriptions == null) + filterDescriptions = new HashMap>(10); + LinkedList descList = filterDescriptions.get(path); + if (descList == null) { + descList = new LinkedList(); + filterDescriptions.put(path, descList); + } + descList.add(description); + } + + /** + * Add the description of a filter. Setting to a description of null will + * remove the filter from the project description. + */ + synchronized public void removeFilter(IPath path, FilterDescription description) { + if (filterDescriptions != null) { + LinkedList descList = filterDescriptions.get(path); + if (descList != null) { + descList.remove(description); + if (descList.size() == 0) { + filterDescriptions.remove(path); + if (filterDescriptions.size() == 0) + filterDescriptions = null; + } + } + } + } + + /** + * Sets the description of a variable. Setting to a description of null will + * remove the variable from the project description. + * @return true if the description was actually changed, + * false otherwise. + * @since 3.5 + */ + @SuppressWarnings({"unchecked"}) + public boolean setVariableDescription(String name, VariableDescription description) { + HashMap tempMap = variableDescriptions; + if (description != null) { + // addition or modification + if (tempMap == null) + tempMap = new HashMap(10); + else + // copy on write to protect against concurrent read + tempMap = (HashMap) tempMap.clone(); + Object oldValue = tempMap.put(name, description); + if (oldValue != null && description.equals(oldValue)) { + //not actually changed anything + return false; + } + variableDescriptions = tempMap; + } else { + // removal + if (tempMap == null) + return false; + // copy on write to protect against concurrent access + HashMap newMap = (HashMap) tempMap.clone(); + Object oldValue = newMap.remove(name); + if (oldValue == null) { + //not actually changed anything + return false; + } + variableDescriptions = newMap.size() == 0 ? null : newMap; + } + return true; + } + + /** + * set the filters for a given resource. Setting to a description of null will + * remove the filter from the project description. + * @return true if the description was actually changed, + * false otherwise. + */ + synchronized public boolean setFilters(IPath path, LinkedList descriptions) { + if (descriptions != null) { + // addition + if (filterDescriptions == null) + filterDescriptions = new HashMap>(10); + Object oldValue = filterDescriptions.put(path, descriptions); + if (oldValue != null && descriptions.equals(oldValue)) { + //not actually changed anything + return false; + } + } else { + // removal + if (filterDescriptions == null) + return false; + + Object oldValue = filterDescriptions.remove(path); + if (oldValue == null) { + //not actually changed anything + return false; + } + if (filterDescriptions.size() == 0) + filterDescriptions = null; + } + return true; + } + + /* (non-Javadoc) + * @see IProjectDescription#setLocation(IPath) + */ + @Override + public void setLocation(IPath path) { + this.location = path == null ? null : URIUtil.toURI(path); + } + + @Override + public void setLocationURI(URI location) { + this.location = location; + } + + /* (non-Javadoc) + * @see IProjectDescription#setName(String) + */ + @Override + public void setName(String value) { + super.setName(value); + } + + /* (non-Javadoc) + * @see IProjectDescription#setNatureIds(String[]) + */ + @Override + public void setNatureIds(String[] value) { + natures = value.clone(); + } + + /* (non-Javadoc) + * @see IProjectDescription#setReferencedProjects(IProject[]) + */ + @Override + public void setReferencedProjects(IProject[] value) { + Assert.isLegal(value != null); + staticRefs = copyAndRemoveDuplicates(value); + clearCachedReferences(null); + } + + /** + * Sets the location URI for a project snapshot that may be + * loaded automatically when the project is created in a workspace. + *

+ * EXPERIMENTAL. This method has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. Please do not use this API without + * consulting with the Platform Core team. + *

+ * @param snapshotLocation the location URI or + * null to clear the setting + * @see IProject#loadSnapshot(int, URI, IProgressMonitor) + * @see #getSnapshotLocationURI() + * @since 3.6 + */ + public void setSnapshotLocationURI(URI snapshotLocation) { + this.snapshotLocation = snapshotLocation; + } + + public URI getGroupLocationURI(IPath projectRelativePath) { + return LinkDescription.VIRTUAL_LOCATION; + } + + /** + * Updates the dynamic build configuration and reference state to that of the passed in + * description. + * Copies in: + *
    + *
  • Active configuration name
  • + *
  • Dynamic Project References
  • + *
  • Build configurations list
  • + *
  • Build Configuration References
  • + *
+ * @param description Project description to copy dynamic state from + * @return boolean indicating if anything changed requing re-calculation of WS build order + */ + public boolean updateDynamicState(ProjectDescription description) { + boolean changed = false; + if (!activeConfiguration.equals(description.activeConfiguration)) { + changed = true; + activeConfiguration = description.activeConfiguration; + } + if (!Arrays.equals(dynamicRefs, description.dynamicRefs)) { + changed = true; + setDynamicReferences(description.dynamicRefs); + } + if (!Arrays.equals(configNames, description.configNames)) { + changed = true; + setBuildConfigs(description.configNames); + } + if (configRefsHaveChanges(dynamicConfigRefs, description.dynamicConfigRefs)) { + changed = true; + dynamicConfigRefs = new HashMap(description.dynamicConfigRefs); + } + if (changed) + clearCachedReferences(null); + return changed; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java new file mode 100644 index 0000000000..4cb1e03918 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java @@ -0,0 +1,1117 @@ +/******************************************************************************* + * 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 + * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import javax.xml.parsers.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.internal.localstore.SafeFileInputStream; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; +import org.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Reads serialized project descriptions. + * + * Note: Suppress warnings on whole class because of unusual use of objectStack. + */ +@SuppressWarnings({"unchecked"}) +public class ProjectDescriptionReader extends DefaultHandler implements IModelObjectConstants { + + //states + protected static final int S_BUILD_COMMAND = 0; + protected static final int S_BUILD_COMMAND_ARGUMENTS = 1; + protected static final int S_BUILD_COMMAND_NAME = 2; + protected static final int S_BUILD_COMMAND_TRIGGERS = 3; + protected static final int S_BUILD_SPEC = 4; + protected static final int S_DICTIONARY = 5; + protected static final int S_DICTIONARY_KEY = 6; + protected static final int S_DICTIONARY_VALUE = 7; + protected static final int S_INITIAL = 8; + protected static final int S_LINK = 9; + protected static final int S_LINK_LOCATION = 10; + protected static final int S_LINK_LOCATION_URI = 11; + protected static final int S_LINK_PATH = 12; + protected static final int S_LINK_TYPE = 13; + protected static final int S_LINKED_RESOURCES = 14; + protected static final int S_NATURE_NAME = 15; + protected static final int S_NATURES = 16; + protected static final int S_PROJECT_COMMENT = 17; + protected static final int S_PROJECT_DESC = 18; + protected static final int S_PROJECT_NAME = 19; + protected static final int S_PROJECTS = 20; + protected static final int S_REFERENCED_PROJECT_NAME = 21; + + protected static final int S_FILTERED_RESOURCES = 23; + protected static final int S_FILTER = 24; + protected static final int S_FILTER_ID = 25; + protected static final int S_FILTER_PATH = 26; + protected static final int S_FILTER_TYPE = 27; + + protected static final int S_MATCHER = 28; + protected static final int S_MATCHER_ID = 29; + protected static final int S_MATCHER_ARGUMENTS = 30; + + protected static final int S_VARIABLE_LIST = 31; + protected static final int S_VARIABLE = 32; + protected static final int S_VARIABLE_NAME = 33; + protected static final int S_VARIABLE_VALUE = 34; + + protected static final int S_SNAPSHOT_LOCATION = 35; + + /** + * Singleton sax parser factory + */ + private static SAXParserFactory singletonParserFactory; + + /** + * Singleton sax parser + */ + private static SAXParser singletonParser; + + protected final StringBuffer charBuffer = new StringBuffer(); + + protected Stack objectStack; + protected MultiStatus problems; + + /** + * The project we are reading the description for, or null if unknown. + */ + private final IProject project; + // The project description we are creating. + ProjectDescription projectDescription = null; + + protected int state = S_INITIAL; + + /** + * Returns the SAXParser to use when parsing project description files. + * @throws ParserConfigurationException + * @throws SAXException + */ + private static synchronized SAXParser createParser() throws ParserConfigurationException, SAXException { + //the parser can't be used concurrently, so only use singleton when workspace is locked + if (!isWorkspaceLocked()) + return createParserFactory().newSAXParser(); + if (singletonParser == null) { + singletonParser = createParserFactory().newSAXParser(); + } + return singletonParser; + } + + /** + * Returns the SAXParserFactory to use when parsing project description files. + * @throws ParserConfigurationException + */ + private static synchronized SAXParserFactory createParserFactory() throws ParserConfigurationException { + if (singletonParserFactory == null) { + singletonParserFactory = SAXParserFactory.newInstance(); + singletonParserFactory.setNamespaceAware(true); + try { + singletonParserFactory.setFeature("http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$ + } catch (SAXException e) { + // In case support for this feature is removed + } + } + return singletonParserFactory; + } + + private static boolean isWorkspaceLocked() { + try { + return ((Workspace) ResourcesPlugin.getWorkspace()).getWorkManager().isLockAlreadyAcquired(); + } catch (CoreException e) { + return false; + } + } + + public ProjectDescriptionReader() { + this.project = null; + } + + public ProjectDescriptionReader(IProject project) { + this.project = project; + } + + /** + * @see ContentHandler#characters(char[], int, int) + */ + @Override + public void characters(char[] chars, int offset, int length) { + //accumulate characters and process them when endElement is reached + charBuffer.append(chars, offset, length); + } + + /** + * End of an element that is part of a build command + */ + private void endBuildCommandElement(String elementName) { + if (elementName.equals(BUILD_COMMAND)) { + // Pop this BuildCommand off the stack. + BuildCommand command = (BuildCommand) objectStack.pop(); + // Add this BuildCommand to a array list of BuildCommands. + ArrayList commandList = (ArrayList) objectStack.peek(); + commandList.add(command); + state = S_BUILD_SPEC; + } + } + + /** + * End of an element that is part of a build spec + */ + private void endBuildSpecElement(String elementName) { + if (elementName.equals(BUILD_SPEC)) { + // Pop off the array list of BuildCommands and add them to the + // ProjectDescription which is the next thing on the stack. + ArrayList commands = (ArrayList) objectStack.pop(); + state = S_PROJECT_DESC; + if (commands.isEmpty()) + return; + ICommand[] commandArray = commands.toArray(new ICommand[commands.size()]); + projectDescription.setBuildSpec(commandArray); + } + } + + /** + * End a build triggers element and set the triggers for the current + * build command element. + */ + private void endBuildTriggersElement(String elementName) { + if (elementName.equals(BUILD_TRIGGERS)) { + state = S_BUILD_COMMAND; + BuildCommand command = (BuildCommand) objectStack.peek(); + //presence of this element indicates the builder is configurable + command.setConfigurable(true); + //clear all existing values + command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, false); + + //set new values according to value in the triggers element + StringTokenizer tokens = new StringTokenizer(charBuffer.toString(), ","); //$NON-NLS-1$ + while (tokens.hasMoreTokens()) { + String next = tokens.nextToken(); + if (next.toLowerCase().equals(TRIGGER_AUTO)) { + command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, true); + } else if (next.toLowerCase().equals(TRIGGER_CLEAN)) { + command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, true); + } else if (next.toLowerCase().equals(TRIGGER_FULL)) { + command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, true); + } else if (next.toLowerCase().equals(TRIGGER_INCREMENTAL)) { + command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, true); + } + } + } + } + + /** + * End of a dictionary element + */ + private void endDictionary(String elementName) { + if (elementName.equals(DICTIONARY)) { + // Pick up the value and then key off the stack and add them + // to the HashMap which is just below them on the stack. + // Leave the HashMap on the stack to pick up more key/value + // pairs if they exist. + String value = (String) objectStack.pop(); + String key = (String) objectStack.pop(); + ((HashMap) objectStack.peek()).put(key, value); + state = S_BUILD_COMMAND_ARGUMENTS; + } + } + + private void endDictionaryKey(String elementName) { + if (elementName.equals(KEY)) { + // There is a value place holder on the top of the stack and + // a key place holder just below it. + String value = (String) objectStack.pop(); + String oldKey = (String) objectStack.pop(); + String newKey = charBuffer.toString(); + if (oldKey != null && oldKey.length() != 0) { + parseProblem(NLS.bind(Messages.projRead_whichKey, oldKey, newKey)); + objectStack.push(oldKey); + } else { + objectStack.push(newKey); + } + //push back the dictionary value + objectStack.push(value); + state = S_DICTIONARY; + } + } + + private void endDictionaryValue(String elementName) { + if (elementName.equals(VALUE)) { + String newValue = charBuffer.toString(); + // There is a value place holder on the top of the stack + String oldValue = (String) objectStack.pop(); + if (oldValue != null && oldValue.length() != 0) { + parseProblem(NLS.bind(Messages.projRead_whichValue, oldValue, newValue)); + objectStack.push(oldValue); + } else { + objectStack.push(newValue); + } + state = S_DICTIONARY; + } + } + + /** + * @see ContentHandler#endElement(String, String, String) + */ + @Override + public void endElement(String uri, String elementName, String qname) { + switch (state) { + case S_PROJECT_DESC : + // Don't think we need to do anything here. + break; + case S_PROJECT_NAME : + if (elementName.equals(NAME)) { + // Project names cannot have leading/trailing whitespace + // as they are IResource names. + projectDescription.setName(charBuffer.toString().trim()); + state = S_PROJECT_DESC; + } + break; + case S_PROJECTS : + if (elementName.equals(PROJECTS)) { + endProjectsElement(elementName); + state = S_PROJECT_DESC; + } + break; + case S_DICTIONARY : + endDictionary(elementName); + break; + case S_BUILD_COMMAND_ARGUMENTS : + if (elementName.equals(ARGUMENTS)) { + // There is a hashmap on the top of the stack with the + // arguments (if any). + HashMap dictionaryArgs = (HashMap) objectStack.pop(); + state = S_BUILD_COMMAND; + if (dictionaryArgs.isEmpty()) + break; + // Below the hashMap on the stack, there is a BuildCommand. + ((BuildCommand) objectStack.peek()).setArguments(dictionaryArgs); + } + break; + case S_BUILD_COMMAND : + endBuildCommandElement(elementName); + break; + case S_BUILD_SPEC : + endBuildSpecElement(elementName); + break; + case S_BUILD_COMMAND_TRIGGERS : + endBuildTriggersElement(elementName); + break; + case S_NATURES : + endNaturesElement(elementName); + break; + case S_LINK : + endLinkElement(elementName); + break; + case S_LINKED_RESOURCES : + endLinkedResourcesElement(elementName); + break; + case S_VARIABLE : + endVariableElement(elementName); + break; + case S_FILTER : + endFilterElement(elementName); + break; + case S_FILTERED_RESOURCES : + endFilteredResourcesElement(elementName); + break; + case S_VARIABLE_LIST : + endVariableListElement(elementName); + break; + case S_PROJECT_COMMENT : + if (elementName.equals(COMMENT)) { + projectDescription.setComment(charBuffer.toString()); + state = S_PROJECT_DESC; + } + break; + case S_REFERENCED_PROJECT_NAME : + if (elementName.equals(PROJECT)) { + //top of stack is list of project references + // Referenced projects are just project names and, therefore, + // are also IResource names and cannot have leading/trailing + // whitespace. + ((ArrayList) objectStack.peek()).add(charBuffer.toString().trim()); + state = S_PROJECTS; + } + break; + case S_BUILD_COMMAND_NAME : + if (elementName.equals(NAME)) { + //top of stack is the build command + // A build command name is an extension id and + // cannot have leading/trailing whitespace. + ((BuildCommand) objectStack.peek()).setName(charBuffer.toString().trim()); + state = S_BUILD_COMMAND; + } + break; + case S_DICTIONARY_KEY : + endDictionaryKey(elementName); + break; + case S_DICTIONARY_VALUE : + endDictionaryValue(elementName); + break; + case S_NATURE_NAME : + if (elementName.equals(NATURE)) { + //top of stack is list of nature names + // A nature name is an extension id and cannot + // have leading/trailing whitespace. + ((ArrayList) objectStack.peek()).add(charBuffer.toString().trim()); + state = S_NATURES; + } + break; + case S_LINK_PATH : + endLinkPath(elementName); + break; + case S_LINK_TYPE : + endLinkType(elementName); + break; + case S_LINK_LOCATION : + endLinkLocation(elementName); + break; + case S_LINK_LOCATION_URI : + endLinkLocationURI(elementName); + break; + case S_FILTER_ID : + endFilterId(elementName); + break; + case S_FILTER_PATH : + endFilterPath(elementName); + break; + case S_FILTER_TYPE : + endFilterType(elementName); + break; + case S_MATCHER : + endMatcherElement(elementName); + break; + case S_MATCHER_ID : + endMatcherID(elementName); + break; + case S_MATCHER_ARGUMENTS : + endMatcherArguments(elementName); + break; + case S_VARIABLE_NAME : + endVariableName(elementName); + break; + case S_VARIABLE_VALUE : + endVariableValue(elementName); + break; + case S_SNAPSHOT_LOCATION : + endSnapshotLocation(elementName); + break; + } + charBuffer.setLength(0); + } + + /** + * End this group of linked resources and add them to the project description. + */ + private void endLinkedResourcesElement(String elementName) { + if (elementName.equals(LINKED_RESOURCES)) { + HashMap linkedResources = (HashMap) objectStack.pop(); + state = S_PROJECT_DESC; + if (linkedResources.isEmpty()) + return; + projectDescription.setLinkDescriptions(linkedResources); + } + } + + /** + * End this group of linked resources and add them to the project description. + */ + private void endFilteredResourcesElement(String elementName) { + if (elementName.equals(FILTERED_RESOURCES)) { + HashMap> filteredResources = (HashMap>) objectStack.pop(); + state = S_PROJECT_DESC; + if (filteredResources.isEmpty()) + return; + projectDescription.setFilterDescriptions(filteredResources); + } + } + + /** + * End this group of group resources and add them to the project + * description. + */ + private void endVariableListElement(String elementName) { + if (elementName.equals(VARIABLE_LIST)) { + HashMap variableList = (HashMap) objectStack.pop(); + state = S_PROJECT_DESC; + if (variableList.isEmpty()) + return; + projectDescription.setVariableDescriptions(variableList); + } + } + + /** + * End a single linked resource and add it to the HashMap. + */ + private void endLinkElement(String elementName) { + if (elementName.equals(LINK)) { + state = S_LINKED_RESOURCES; + // Pop off the link description + LinkDescription link = (LinkDescription) objectStack.pop(); + // Make sure that you have something reasonable + IPath path = link.getProjectRelativePath(); + int type = link.getType(); + URI location = link.getLocationURI(); + if (location == null) { + parseProblem(NLS.bind(Messages.projRead_badLinkLocation, path, Integer.toString(type))); + return; + } + if ((path == null) || path.segmentCount() == 0) { + parseProblem(NLS.bind(Messages.projRead_emptyLinkName, Integer.toString(type), location)); + return; + } + if (type == -1) { + parseProblem(NLS.bind(Messages.projRead_badLinkType, path, location)); + return; + } + + // The HashMap of linked resources is the next thing on the stack + ((HashMap) objectStack.peek()).put(link.getProjectRelativePath(), link); + } + } + + private void endMatcherElement(String elementName) { + if (elementName.equals(MATCHER)) { + // Pop off an array (Object[2]) containing the matcher id and arguments. + Object[] matcher = (Object[]) objectStack.pop(); + // Make sure that you have something reasonable + String id = (String) matcher[0]; + // the id can't be null + if (id == null) { + parseProblem(NLS.bind(Messages.projRead_badFilterID, id)); + return; + } + + if (objectStack.peek() instanceof ArrayList) { + state = S_MATCHER_ARGUMENTS; + // The ArrayList of matchers is the next thing on the stack + ArrayList list = ((ArrayList) objectStack.peek()); + list.add(new FileInfoMatcherDescription((String) matcher[0], matcher[1])); + } + + if (objectStack.peek() instanceof FilterDescription) { + state = S_FILTER; + FilterDescription d = ((FilterDescription) objectStack.peek()); + d.setFileInfoMatcherDescription(new FileInfoMatcherDescription((String) matcher[0], matcher[1])); + } + } + } + + /** + * End a single filtered resource and add it to the HashMap. + */ + private void endFilterElement(String elementName) { + if (elementName.equals(FILTER)) { + // Pop off the filter description + FilterDescription filter = (FilterDescription) objectStack.pop(); + if (project != null) { + // Make sure that you have something reasonable + IPath path = filter.getResource().getProjectRelativePath(); + int type = filter.getType(); + // arguments can be null + if (path == null) { + parseProblem(NLS.bind(Messages.projRead_emptyFilterName, Integer.toString(type))); + return; + } + if (type == -1) { + parseProblem(NLS.bind(Messages.projRead_badFilterType, path)); + return; + } + + // The HashMap of filtered resources is the next thing on the stack + HashMap> map = ((HashMap>) objectStack.peek()); + LinkedList list = map.get(filter.getResource().getProjectRelativePath()); + if (list == null) { + list = new LinkedList(); + map.put(filter.getResource().getProjectRelativePath(), list); + } + list.add(filter); + } else { + // if the project is null, that means that we're loading a project description to retrieve + // some meta data only. + String key = new String(); // an empty key; + HashMap> map = ((HashMap>) objectStack.peek()); + LinkedList list = map.get(key); + if (list == null) { + list = new LinkedList(); + map.put(key, list); + } + list.add(filter); + } + state = S_FILTERED_RESOURCES; + } + } + + /** + * End a single group resource and add it to the HashMap. + */ + private void endVariableElement(String elementName) { + if (elementName.equals(VARIABLE)) { + state = S_VARIABLE_LIST; + // Pop off the link description + VariableDescription desc = (VariableDescription) objectStack.pop(); + // Make sure that you have something reasonable + if (desc.getName().length() == 0) { + parseProblem(NLS.bind(Messages.projRead_emptyVariableName, project.getName())); + return; + } + + // The HashMap of variables is the next thing on the stack + ((HashMap) objectStack.peek()).put(desc.getName(), desc); + } + } + + /** + * For backwards compatibility, link locations in the local file system are represented + * in the project description under the "location" tag. + * @param elementName + */ + private void endLinkLocation(String elementName) { + if (elementName.equals(LOCATION)) { + // A link location is an URI. URIs cannot have leading/trailing whitespace + String newLocation = charBuffer.toString().trim(); + // objectStack has a LinkDescription on it. Set the type on this LinkDescription. + URI oldLocation = ((LinkDescription) objectStack.peek()).getLocationURI(); + if (oldLocation != null) { + parseProblem(NLS.bind(Messages.projRead_badLocation, oldLocation, newLocation)); + } else { + ((LinkDescription) objectStack.peek()).setLocationURI(URIUtil.toURI(Path.fromPortableString(newLocation))); + } + state = S_LINK; + } + } + + /** + * Link locations that are not stored in the local file system are represented + * in the project description under the "locationURI" tag. + * @param elementName + */ + private void endLinkLocationURI(String elementName) { + if (elementName.equals(LOCATION_URI)) { + // A link location is an URI. URIs cannot have leading/trailing whitespace + String newLocation = charBuffer.toString().trim(); + // objectStack has a LinkDescription on it. Set the type on this LinkDescription. + URI oldLocation = ((LinkDescription) objectStack.peek()).getLocationURI(); + if (oldLocation != null) { + parseProblem(NLS.bind(Messages.projRead_badLocation, oldLocation, newLocation)); + } else { + try { + ((LinkDescription) objectStack.peek()).setLocationURI(new URI(newLocation)); + } catch (URISyntaxException e) { + String msg = Messages.projRead_failureReadingProjectDesc; + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, msg, e)); + } + } + state = S_LINK; + } + } + + private void endLinkPath(String elementName) { + if (elementName.equals(NAME)) { + IPath newPath = new Path(charBuffer.toString()); + // objectStack has a LinkDescription on it. Set the name + // on this LinkDescription. + IPath oldPath = ((LinkDescription) objectStack.peek()).getProjectRelativePath(); + if (oldPath.segmentCount() != 0) { + parseProblem(NLS.bind(Messages.projRead_badLinkName, oldPath, newPath)); + } else { + ((LinkDescription) objectStack.peek()).setPath(newPath); + } + state = S_LINK; + } + } + + private void endMatcherID(String elementName) { + if (elementName.equals(ID)) { + // The matcher id is String. + String newID = charBuffer.toString().trim(); + // objectStack has an array (Object[2]) on it for the matcher id and arguments. + String oldID = (String) ((Object[]) objectStack.peek())[0]; + if (oldID != null) { + parseProblem(NLS.bind(Messages.projRead_badID, oldID, newID)); + } else { + ((Object[]) objectStack.peek())[0] = newID; + } + state = S_MATCHER; + } + } + + private void endMatcherArguments(String elementName) { + if (elementName.equals(ARGUMENTS)) { + ArrayList matchers = (ArrayList) objectStack.pop(); + Object newArguments = charBuffer.toString(); + + if (matchers.size() > 0) + newArguments = matchers.toArray(new FileInfoMatcherDescription[matchers.size()]); + + // objectStack has an array (Object[2]) on it for the matcher id and arguments. + String oldArguments = (String) ((Object[]) objectStack.peek())[1]; + if (oldArguments != null) { + parseProblem(NLS.bind(Messages.projRead_badArguments, oldArguments, newArguments)); + } else + ((Object[]) objectStack.peek())[1] = newArguments; + state = S_MATCHER; + } + } + + private void endFilterId(String elementName) { + if (elementName.equals(ID)) { + Long newId = new Long(charBuffer.toString()); + // objectStack has a FilterDescription on it. Set the name + // on this FilterDescription. + long oldId = ((FilterDescription) objectStack.peek()).getId(); + if (oldId != 0) { + parseProblem(NLS.bind(Messages.projRead_badFilterName, new Long(oldId), newId)); + } else { + ((FilterDescription) objectStack.peek()).setId(newId.longValue()); + } + state = S_FILTER; + } + } + + private void endFilterPath(String elementName) { + if (elementName.equals(NAME)) { + IPath newPath = new Path(charBuffer.toString()); + // objectStack has a FilterDescription on it. Set the name + // on this FilterDescription. + IResource oldResource = ((FilterDescription) objectStack.peek()).getResource(); + if (oldResource != null) { + parseProblem(NLS.bind(Messages.projRead_badFilterName, oldResource.getProjectRelativePath(), newPath)); + } else { + if (project != null) { + ((FilterDescription) objectStack.peek()).setResource(newPath.isEmpty() ? (IResource) project : project.getFolder(newPath)); + } else { + // if the project is null, that means that we're loading a project description to retrieve + // some meta data only. + ((FilterDescription) objectStack.peek()).setResource(null); + } + } + state = S_FILTER; + } + } + + private void endFilterType(String elementName) { + if (elementName.equals(TYPE)) { + int newType = -1; + try { + // parseInt expects a string containing only numerics + // or a leading '-'. Ensure there is no leading/trailing + // whitespace. + newType = Integer.parseInt(charBuffer.toString().trim()); + } catch (NumberFormatException e) { + log(e); + } + // objectStack has a FilterDescription on it. Set the type + // on this FilterDescription. + int oldType = ((FilterDescription) objectStack.peek()).getType(); + if (oldType != -1) { + parseProblem(NLS.bind(Messages.projRead_badFilterType2, Integer.toString(oldType), Integer.toString(newType))); + } else { + ((FilterDescription) objectStack.peek()).setType(newType); + } + state = S_FILTER; + } + } + + private void endVariableName(String elementName) { + if (elementName.equals(NAME)) { + String value = charBuffer.toString(); + // objectStack has a VariableDescription on it. Set the value + // on this ValueDescription. + ((VariableDescription) objectStack.peek()).setName(value); + state = S_VARIABLE; + } + } + + private void endVariableValue(String elementName) { + if (elementName.equals(VALUE)) { + String value = charBuffer.toString(); + // objectStack has a VariableDescription on it. Set the value + // on this ValueDescription. + ((VariableDescription) objectStack.peek()).setValue(value); + state = S_VARIABLE; + } + } + + private void endLinkType(String elementName) { + if (elementName.equals(TYPE)) { + //FIXME we should handle this case by removing the entire link + //for now we default to a file link + int newType = IResource.FILE; + try { + // parseInt expects a string containing only numerics + // or a leading '-'. Ensure there is no leading/trailing + // whitespace. + newType = Integer.parseInt(charBuffer.toString().trim()); + } catch (NumberFormatException e) { + log(e); + } + // objectStack has a LinkDescription on it. Set the type + // on this LinkDescription. + int oldType = ((LinkDescription) objectStack.peek()).getType(); + if (oldType != -1) { + parseProblem(NLS.bind(Messages.projRead_badLinkType2, Integer.toString(oldType), Integer.toString(newType))); + } else { + ((LinkDescription) objectStack.peek()).setType(newType); + } + state = S_LINK; + } + } + + /** + * End of an element that is part of a nature list + */ + private void endNaturesElement(String elementName) { + if (elementName.equals(NATURES)) { + // Pop the array list of natures off the stack + ArrayList natures = (ArrayList) objectStack.pop(); + state = S_PROJECT_DESC; + if (natures.size() == 0) + return; + String[] natureNames = natures.toArray(new String[natures.size()]); + projectDescription.setNatureIds(natureNames); + } + } + + /** + * End of an element that is part of a project references list + */ + private void endProjectsElement(String elementName) { + // Pop the array list that contains all the referenced project names + ArrayList referencedProjects = (ArrayList) objectStack.pop(); + if (referencedProjects.size() == 0) + // Don't bother adding an empty group of referenced projects to the + // project descriptor. + return; + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IProject[] projects = new IProject[referencedProjects.size()]; + for (int i = 0; i < projects.length; i++) { + projects[i] = root.getProject(referencedProjects.get(i)); + } + projectDescription.setReferencedProjects(projects); + } + + private void endSnapshotLocation(String elementName) { + if (elementName.equals(SNAPSHOT_LOCATION)) { + String location = charBuffer.toString().trim(); + try { + projectDescription.setSnapshotLocationURI(new URI(location)); + } catch (URISyntaxException e) { + String msg = NLS.bind(Messages.projRead_badSnapshotLocation, location); + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, msg, e)); + } + state = S_PROJECT_DESC; + } + } + + /** + * @see ErrorHandler#error(SAXParseException) + */ + @Override + public void error(SAXParseException error) { + log(error); + } + + /** + * @see ErrorHandler#fatalError(SAXParseException) + */ + @Override + public void fatalError(SAXParseException error) throws SAXException { + // ensure a null value is not passed as message to Status constructor (bug 42782) + String message = error.getMessage(); + if (project != null) + message = NLS.bind(Messages.resources_readMeta, project.getName()); + problems.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message == null ? "" : message, error)); //$NON-NLS-1$ + throw error; + } + + protected void log(Exception ex) { + String message = ex.getMessage(); + if (project != null) + message = NLS.bind(Messages.resources_readMeta, project.getName()); + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message == null ? "" : message, ex)); //$NON-NLS-1$ + } + + private void parseProblem(String errorMessage) { + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, errorMessage, null)); + } + + private void parseProjectDescription(String elementName) { + if (elementName.equals(NAME)) { + state = S_PROJECT_NAME; + return; + } + if (elementName.equals(COMMENT)) { + state = S_PROJECT_COMMENT; + return; + } + if (elementName.equals(PROJECTS)) { + state = S_PROJECTS; + // Push an array list on the object stack to hold the name + // of all the referenced projects. This array list will be + // popped off the stack, massaged into the right format + // and added to the project description when we hit the + // end element for PROJECTS. + objectStack.push(new ArrayList()); + return; + } + if (elementName.equals(BUILD_SPEC)) { + state = S_BUILD_SPEC; + // Push an array list on the object stack to hold the build commands + // for this build spec. This array list will be popped off the stack, + // massaged into the right format and added to the project's build + // spec when we hit the end element for BUILD_SPEC. + objectStack.push(new ArrayList()); + return; + } + if (elementName.equals(NATURES)) { + state = S_NATURES; + // Push an array list to hold all the nature names. + objectStack.push(new ArrayList()); + return; + } + if (elementName.equals(LINKED_RESOURCES)) { + // Push a HashMap to collect all the links. + objectStack.push(new HashMap()); + state = S_LINKED_RESOURCES; + return; + } + if (elementName.equals(FILTERED_RESOURCES)) { + // Push a HashMap to collect all the filters. + objectStack.push(new HashMap>()); + state = S_FILTERED_RESOURCES; + return; + } + if (elementName.equals(VARIABLE_LIST)) { + // Push a HashMap to collect all the variables. + objectStack.push(new HashMap()); + state = S_VARIABLE_LIST; + return; + } + if (elementName.equals(SNAPSHOT_LOCATION)) { + state = S_SNAPSHOT_LOCATION; + return; + } + } + + public ProjectDescription read(InputSource input) { + problems = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, Messages.projRead_failureReadingProjectDesc, null); + objectStack = new Stack(); + state = S_INITIAL; + try { + createParser().parse(input, this); + } catch (ParserConfigurationException e) { + log(e); + } catch (IOException e) { + log(e); + } catch (SAXException e) { + log(e); + } + + if (projectDescription != null && projectDescription.getName() == null) + parseProblem(Messages.projRead_missingProjectName); + + switch (problems.getSeverity()) { + case IStatus.ERROR : + Policy.log(problems); + return null; + case IStatus.WARNING : + case IStatus.INFO : + Policy.log(problems); + case IStatus.OK : + default : + return projectDescription; + } + } + + /** + * Reads and returns a project description stored at the given location + */ + public ProjectDescription read(IPath location) throws IOException { + BufferedInputStream file = null; + try { + file = new BufferedInputStream(new FileInputStream(location.toFile())); + return read(new InputSource(file)); + } finally { + FileUtil.safeClose(file); + } + } + + /** + * Reads and returns a project description stored at the given location, or + * temporary location. + */ + public ProjectDescription read(IPath location, IPath tempLocation) throws IOException { + SafeFileInputStream file = new SafeFileInputStream(location.toOSString(), tempLocation.toOSString()); + try { + return read(new InputSource(file)); + } finally { + file.close(); + } + } + + /** + * @see ContentHandler#startElement(String, String, String, Attributes) + */ + @Override + public void startElement(String uri, String elementName, String qname, Attributes attributes) throws SAXException { + //clear the character buffer at the start of every element + charBuffer.setLength(0); + switch (state) { + case S_INITIAL : + if (elementName.equals(PROJECT_DESCRIPTION)) { + state = S_PROJECT_DESC; + projectDescription = new ProjectDescription(); + } else { + throw (new SAXException(NLS.bind(Messages.projRead_notProjectDescription, elementName))); + } + break; + case S_PROJECT_DESC : + parseProjectDescription(elementName); + break; + case S_PROJECTS : + if (elementName.equals(PROJECT)) { + state = S_REFERENCED_PROJECT_NAME; + } + break; + case S_BUILD_SPEC : + if (elementName.equals(BUILD_COMMAND)) { + state = S_BUILD_COMMAND; + objectStack.push(new BuildCommand()); + } + break; + case S_BUILD_COMMAND : + if (elementName.equals(NAME)) { + state = S_BUILD_COMMAND_NAME; + } else if (elementName.equals(BUILD_TRIGGERS)) { + state = S_BUILD_COMMAND_TRIGGERS; + } else if (elementName.equals(ARGUMENTS)) { + state = S_BUILD_COMMAND_ARGUMENTS; + // Push a HashMap to hold all the key/value pairs which + // will become the argument list. + objectStack.push(new HashMap()); + } + break; + case S_BUILD_COMMAND_ARGUMENTS : + if (elementName.equals(DICTIONARY)) { + state = S_DICTIONARY; + // Push 2 strings for the key/value pair to be read + objectStack.push(new String()); // key + objectStack.push(new String()); // value + } + break; + case S_DICTIONARY : + if (elementName.equals(KEY)) { + state = S_DICTIONARY_KEY; + } else if (elementName.equals(VALUE)) { + state = S_DICTIONARY_VALUE; + } + break; + case S_NATURES : + if (elementName.equals(NATURE)) { + state = S_NATURE_NAME; + } + break; + case S_LINKED_RESOURCES : + if (elementName.equals(LINK)) { + state = S_LINK; + // Push place holders for the name, type and location of + // this link. + objectStack.push(new LinkDescription()); + } + break; + case S_VARIABLE_LIST : + if (elementName.equals(VARIABLE)) { + state = S_VARIABLE; + // Push place holders for the name, type and location of + // this link. + objectStack.push(new VariableDescription()); + } + break; + case S_LINK : + if (elementName.equals(NAME)) { + state = S_LINK_PATH; + } else if (elementName.equals(TYPE)) { + state = S_LINK_TYPE; + } else if (elementName.equals(LOCATION)) { + state = S_LINK_LOCATION; + } else if (elementName.equals(LOCATION_URI)) { + state = S_LINK_LOCATION_URI; + } + break; + case S_FILTERED_RESOURCES : + if (elementName.equals(FILTER)) { + state = S_FILTER; + // Push place holders for the name, type, id and arguments of + // this filter. + objectStack.push(new FilterDescription()); + } + break; + case S_FILTER : + if (elementName.equals(ID)) { + state = S_FILTER_ID; + } else if (elementName.equals(NAME)) { + state = S_FILTER_PATH; + } else if (elementName.equals(TYPE)) { + state = S_FILTER_TYPE; + } else if (elementName.equals(MATCHER)) { + state = S_MATCHER; + // Push an array for the matcher id and arguments + objectStack.push(new Object[2]); + } + break; + case S_MATCHER : + if (elementName.equals(ID)) { + state = S_MATCHER_ID; + } else if (elementName.equals(ARGUMENTS)) { + state = S_MATCHER_ARGUMENTS; + objectStack.push(new ArrayList()); + } + break; + case S_MATCHER_ARGUMENTS : + if (elementName.equals(MATCHER)) { + state = S_MATCHER; + // Push an array for the matcher id and arguments + objectStack.push(new Object[2]); + } + break; + case S_VARIABLE : + if (elementName.equals(NAME)) { + state = S_VARIABLE_NAME; + } else if (elementName.equals(VALUE)) { + state = S_VARIABLE_VALUE; + } + break; + } + } + + /** + * @see ErrorHandler#warning(SAXParseException) + */ + @Override + public void warning(SAXParseException error) { + log(error); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java new file mode 100644 index 0000000000..064be5804f --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * 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.util.HashMap; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.resources.ICommand; +import org.eclipse.core.resources.IProjectNature; +import org.eclipse.core.runtime.content.IContentTypeMatcher; + +public class ProjectInfo extends ResourceInfo { + + /** The description of this object */ + protected ProjectDescription description = null; + + /** The list of natures for this project */ + protected HashMap natures = null; + + /** The property store for this resource (used only by the compatibility fragment) */ + protected Object propertyStore = null; + + /** The content type matcher for this project. */ + protected IContentTypeMatcher matcher = null; + + /** + * Discards stale natures on this project after project description + * has changed. + */ + public synchronized void discardNatures() { + natures = null; + } + + /** + * Discards any stale state on this project after it has been moved. Builder + * instances must be cleared because they reference the old project handle. + */ + public synchronized void fixupAfterMove() { + natures = null; + // note that the property store instance will be recreated lazily + propertyStore = null; + if (description != null) { + ICommand[] buildSpec = description.getBuildSpec(false); + for (int i = 0; i < buildSpec.length; i++) + ((BuildCommand) buildSpec[i]).setBuilders(null); + } + } + + /** + * Returns the description associated with this info. The return value may be null. + */ + public ProjectDescription getDescription() { + return description; + } + + /** + * Returns the content type matcher associated with this info. The return value may be null. + */ + public IContentTypeMatcher getMatcher() { + return matcher; + } + + public IProjectNature getNature(String natureId) { + // thread safety: (Concurrency001) + HashMap temp = natures; + if (temp == null) + return null; + return temp.get(natureId); + } + + /** + * Returns the property store associated with this info. The return value may be null. + */ + @Override + public Object getPropertyStore() { + return propertyStore; + } + + /** + * Sets the description associated with this info. The value may be null. + */ + public void setDescription(ProjectDescription value) { + if (description != null) { + //if we already have a description, assign the new + //build spec on top of the old one to ensure we maintain + //any existing builder instances in the old build commands + ICommand[] oldSpec = description.buildSpec; + ICommand[] newSpec = value.buildSpec; + value.buildSpec = oldSpec; + value.setBuildSpec(newSpec); + } + description = value; + } + + /** + * Sets the content type matcher to be associated with this info. The value may be null. + */ + public void setMatcher(IContentTypeMatcher matcher) { + this.matcher = matcher; + } + + @SuppressWarnings({"unchecked"}) + public synchronized void setNature(String natureId, IProjectNature value) { + // thread safety: (Concurrency001) + if (value == null) { + if (natures == null) + return; + HashMap temp = (HashMap) natures.clone(); + temp.remove(natureId); + if (temp.isEmpty()) + natures = null; + else + natures = temp; + } else { + HashMap temp = natures; + if (temp == null) + temp = new HashMap(5); + else + temp = (HashMap) natures.clone(); + temp.put(natureId, value); + natures = temp; + } + } + + /** + * Sets the property store associated with this info. The value may be null. + */ + @Override + public void setPropertyStore(Object value) { + propertyStore = value; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java new file mode 100644 index 0000000000..2fa6d7f370 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java @@ -0,0 +1,167 @@ +/******************************************************************************* + * 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.ArrayList; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IProjectNatureDescriptor; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + */ +public class ProjectNatureDescriptor implements IProjectNatureDescriptor { + protected String id; + protected String label; + protected String[] requiredNatures; + protected String[] natureSets; + protected String[] builderIds; + protected String[] contentTypeIds; + protected boolean allowLinking = true; + + //descriptors that are in a dependency cycle are never valid + protected boolean hasCycle = false; + //colours used by cycle detection algorithm + protected byte colour = 0; + + /** + * Creates a new descriptor based on the given extension markup. + * @exception CoreException if the given nature extension is not correctly formed. + */ + protected ProjectNatureDescriptor(IExtension natureExtension) throws CoreException { + readExtension(natureExtension); + } + + protected void fail() throws CoreException { + fail(NLS.bind(Messages.natures_invalidDefinition, id)); + } + + protected void fail(String reason) throws CoreException { + throw new ResourceException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, reason, null)); + } + + /** + * Returns the IDs of the incremental builders that this nature claims to + * own. These builders do not necessarily exist in the registry. + */ + public String[] getBuilderIds() { + return builderIds; + } + + /** + * Returns the IDs of the content types this nature declares to + * have affinity with. These content types do not necessarily exist in the registry. + */ + public String[] getContentTypeIds() { + return contentTypeIds; + } + + /** + * @see IProjectNatureDescriptor#getNatureId() + */ + @Override + public String getNatureId() { + return id; + } + + /** + * @see IProjectNatureDescriptor#getLabel() + */ + @Override + public String getLabel() { + return label; + } + + /** + * @see IProjectNatureDescriptor#getRequiredNatureIds() + */ + @Override + public String[] getRequiredNatureIds() { + return requiredNatures; + } + + /** + * @see IProjectNatureDescriptor#getNatureSetIds() + */ + @Override + public String[] getNatureSetIds() { + return natureSets; + } + + /** + * @see IProjectNatureDescriptor#isLinkingAllowed() + */ + @Override + public boolean isLinkingAllowed() { + return allowLinking; + } + + /** + * Initialize this nature descriptor based on the provided extension point. + */ + protected void readExtension(IExtension natureExtension) throws CoreException { + //read the extension + id = natureExtension.getUniqueIdentifier(); + if (id == null) { + fail(Messages.natures_missingIdentifier); + } + label = natureExtension.getLabel(); + IConfigurationElement[] elements = natureExtension.getConfigurationElements(); + int count = elements.length; + ArrayList requiredList = new ArrayList(count); + ArrayList setList = new ArrayList(count); + ArrayList builderList = new ArrayList(count); + ArrayList contentTypeList = new ArrayList(count); + for (int i = 0; i < count; i++) { + IConfigurationElement element = elements[i]; + String name = element.getName(); + if (name.equalsIgnoreCase("requires-nature")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + requiredList.add(attribute); + } else if (name.equalsIgnoreCase("one-of-nature")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + setList.add(attribute); + } else if (name.equalsIgnoreCase("builder")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + builderList.add(attribute); + } else if (name.equalsIgnoreCase("content-type")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + contentTypeList.add(attribute); + } else if (name.equalsIgnoreCase("options")) { //$NON-NLS-1$ + String attribute = element.getAttribute("allowLinking"); //$NON-NLS-1$ + //when in doubt (missing attribute, wrong value) default to allow linking + allowLinking = !Boolean.FALSE.toString().equalsIgnoreCase(attribute); + } + } + requiredNatures = requiredList.toArray(new String[requiredList.size()]); + natureSets = setList.toArray(new String[setList.size()]); + builderIds = builderList.toArray(new String[builderList.size()]); + contentTypeIds = contentTypeList.toArray(new String[contentTypeList.size()]); + } + + /** + * Prints out a string representation for debugging purposes only. + */ + @Override + public String toString() { + return "ProjectNatureDescriptor(" + id + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java new file mode 100644 index 0000000000..618e0c7f30 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java @@ -0,0 +1,507 @@ +/******************************************************************************* + * 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.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import org.eclipse.core.filesystem.URIUtil; +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; + +/** + * The {@link IPathVariableManager} for a single project + * @see IProject#getPathVariableManager() + */ +public class ProjectPathVariableManager implements IPathVariableManager, IManager { + + private Resource resource; + private ProjectVariableProviderManager.Descriptor variableProviders[] = null; + + /** + * Constructor for the class. + */ + public ProjectPathVariableManager(Resource resource) { + this.resource = resource; + variableProviders = ProjectVariableProviderManager.getDefault().getDescriptors(); + } + + PathVariableManager getWorkspaceManager() { + return (PathVariableManager) resource.getWorkspace().getPathVariableManager(); + } + + /** + * Throws a runtime exception if the given name is not valid as a path + * variable name. + */ + private void checkIsValidName(String name) throws CoreException { + IStatus status = validateName(name); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * Throws an exception if the given path is not valid as a path variable + * value. + */ + private void checkIsValidValue(URI newValue) throws CoreException { + IStatus status = validateValue(newValue); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#getPathVariableNames() + */ + @Override + public String[] getPathVariableNames() { + List result = new LinkedList(); + HashMap map; + try { + map = ((ProjectDescription) resource.getProject().getDescription()).getVariables(); + } catch (CoreException e) { + return new String[0]; + } + for (int i = 0; i < variableProviders.length; i++) { + String[] variableHints = variableProviders[i].getVariableNames(variableProviders[i].getName(), resource); + if (variableHints != null && variableHints.length > 0) + for (int k = 0; k < variableHints.length; k++) + result.add(variableProviders[i].getVariableNames(variableProviders[i].getName(), resource)[k]); + } + if (map != null) + result.addAll(map.keySet()); + result.addAll(Arrays.asList(getWorkspaceManager().getPathVariableNames())); + return result.toArray(new String[0]); + } + + /** + * @deprecated use {@link #getURIValue(String)} instead. + */ + @Deprecated + @Override + public IPath getValue(String varName) { + URI uri = getURIValue(varName); + if (uri != null) + return URIUtil.toPath(uri); + return null; + } + + /** + * If the variable is not listed in the project description, we fall back on + * the workspace variables. + * + * @see org.eclipse.core.resources.IPathVariableManager#getURIValue(String) + */ + @Override + public URI getURIValue(String varName) { + String value = internalGetValue(varName); + if (value != null) { + if (value.indexOf("..") != -1) { //$NON-NLS-1$ + // if the path is 'reducible', lets resolve it first. + int index = value.indexOf(IPath.SEPARATOR); + if (index > 0) { // if its the first character, its an + // absolute path on unix, so we don't + // resolve it + URI resolved = resolveVariable(value); + if (resolved != null) + return resolved; + } + } + try { + return URI.create(value); + } catch (IllegalArgumentException e) { + IPath path = Path.fromPortableString(value); + return URIUtil.toURI(path); + } + } + return getWorkspaceManager().getURIValue(varName); + } + + public String internalGetValue(String varName) { + HashMap map; + try { + map = ((ProjectDescription) resource.getProject().getDescription()).getVariables(); + } catch (CoreException e) { + return null; + } + if (map != null && map.containsKey(varName)) + return map.get(varName).getValue(); + + String name; + int index = varName.indexOf('-'); + if (index != -1) + name = varName.substring(0, index); + else + name = varName; + for (int i = 0; i < variableProviders.length; i++) { + if (variableProviders[i].getName().equals(name)) + return variableProviders[i].getValue(varName, resource); + } + for (int i = 0; i < variableProviders.length; i++) { + if (name.startsWith(variableProviders[i].getName())) + return variableProviders[i].getValue(varName, resource); + } + return null; + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#isDefined(String) + */ + @Override + public boolean isDefined(String varName) { + for (int i = 0; i < variableProviders.length; i++) { + // if (variableProviders[i].getName().equals(varName)) + // return true; + + if (varName.startsWith(variableProviders[i].getName())) + return true; + } + + try { + HashMap map = ((ProjectDescription) resource.getProject().getDescription()).getVariables(); + if (map != null) { + Iterator it = map.keySet().iterator(); + while (it.hasNext()) { + String name = it.next(); + if (name.equals(varName)) + return true; + } + } + } catch (CoreException e) { + return false; + } + boolean value = getWorkspaceManager().isDefined(varName); + if (!value) { + // this is to handle variables with encoded arguments + int index = varName.indexOf('-'); + if (index != -1) { + String newVarName = varName.substring(0, index); + value = isDefined(newVarName); + } + } + return value; + } + + /** + * @deprecated use {@link #resolveURI(URI)} instead. + */ + @Override + @Deprecated + public IPath resolvePath(IPath path) { + if (path == null || path.segmentCount() == 0 || path.isAbsolute() || path.getDevice() != null) + return path; + URI value = resolveURI(URIUtil.toURI(path)); + return value == null ? path : URIUtil.toPath(value); + } + + public URI resolveVariable(String variable) { + LinkedList variableStack = new LinkedList(); + + String value = resolveVariable(variable, variableStack); + if (value != null) { + try { + return URI.create(value); + } catch (IllegalArgumentException e) { + return URIUtil.toURI(Path.fromPortableString(value)); + } + } + return null; + } + + public String resolveVariable(String value, LinkedList variableStack) { + if (variableStack == null) + variableStack = new LinkedList(); + + String tmp = internalGetValue(value); + if (tmp == null) { + URI result = getWorkspaceManager().getURIValue(value); + if (result != null) + return result.toASCIIString(); + } else + value = tmp; + + while (true) { + String stringValue; + try { + URI uri = URI.create(value); + if (uri != null) { + IPath path = URIUtil.toPath(uri); + if (path != null) + stringValue = path.toPortableString(); + else + stringValue = value; + } else + stringValue = value; + } catch (IllegalArgumentException e) { + stringValue = value; + } + // we check if the value contains referenced variables with ${VAR} + int index = stringValue.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = PathVariableUtil.getMatchingBrace(stringValue, index); + String macro = stringValue.substring(index + 2, endIndex); + String resolvedMacro = ""; //$NON-NLS-1$ + if (!variableStack.contains(macro)) { + variableStack.add(macro); + resolvedMacro = resolveVariable(macro, variableStack); + if (resolvedMacro == null) + resolvedMacro = ""; //$NON-NLS-1$ + } + if (stringValue.length() > endIndex) + stringValue = stringValue.substring(0, index) + resolvedMacro + stringValue.substring(endIndex + 1); + else + stringValue = resolvedMacro; + value = stringValue; + } else + break; + } + return value; + } + + @Override + public URI resolveURI(URI uri) { + if (uri == null || uri.isAbsolute() || (uri.getSchemeSpecificPart() == null)) + return uri; + IPath raw = new Path(uri.getSchemeSpecificPart()); + if (raw == null || raw.segmentCount() == 0 || raw.isAbsolute() || raw.getDevice() != null) + return URIUtil.toURI(raw); + URI value = resolveVariable(raw.segment(0)); + if (value == null) + return uri; + + String path = value.getPath(); + if (path != null) { + IPath p = Path.fromPortableString(path); + p = p.append(raw.removeFirstSegments(1)); + try { + value = new URI(value.getScheme(), value.getHost(), p.toPortableString(), value.getFragment()); + } catch (URISyntaxException e) { + return uri; + } + return value; + } + return uri; + } + + /** + * @deprecated use {@link #setURIValue(String, URI)} instead. + */ + @Deprecated + @Override + public void setValue(String varName, IPath newValue) throws CoreException { + if (newValue == null) + setURIValue(varName, (URI) null); + else + setURIValue(varName, URIUtil.toURI(newValue)); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#setValue(String, + * IPath) + */ + @Override + public void setURIValue(String varName, URI newValue) throws CoreException { + checkIsValidName(varName); + checkIsValidValue(newValue); + // read previous value and set new value atomically in order to generate + // the right event + boolean changeWorkspaceValue = false; + Project project = (Project) resource.getProject(); + int eventType = 0; + synchronized (this) { + String value = internalGetValue(varName); + URI currentValue = null; + if (value == null) + currentValue = getWorkspaceManager().getURIValue(varName); + else { + try { + currentValue = URI.create(value); + } catch (IllegalArgumentException e) { + currentValue = null; + } + } + boolean variableExists = currentValue != null; + if (!variableExists && newValue == null) + return; + if (variableExists && currentValue.equals(newValue)) + return; + + for (int i = 0; i < variableProviders.length; i++) { + // if (variableProviders[i].getName().equals(varName)) + // return; + + if (varName.startsWith(variableProviders[i].getName())) + return; + } + + if (value == null && variableExists) + changeWorkspaceValue = true; + else { + IProgressMonitor monitor = new NullProgressMonitor(); + final ISchedulingRule rule = resource.getProject(); // project.workspace.getRuleFactory().modifyRule(project); + try { + project.workspace.prepareOperation(rule, monitor); + project.workspace.beginOperation(true); + // save the location in the project description + ProjectDescription description = project.internalGetDescription(); + if (newValue == null) { + description.setVariableDescription(varName, null); + eventType = IPathVariableChangeEvent.VARIABLE_DELETED; + } else { + description.setVariableDescription(varName, new VariableDescription(varName, newValue.toASCIIString())); + eventType = variableExists ? IPathVariableChangeEvent.VARIABLE_CHANGED : IPathVariableChangeEvent.VARIABLE_CREATED; + } + project.writeDescription(IResource.NONE); + } finally { + project.workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } + } + if (changeWorkspaceValue) + getWorkspaceManager().setURIValue(varName, newValue); + else { + // notify listeners from outside the synchronized block to avoid deadlocks + getWorkspaceManager().fireVariableChangeEvent(project, varName, newValue != null ? URIUtil.toPath(newValue) : null, eventType); + } + } + + /** + * @see org.eclipse.core.internal.resources.IManager#shutdown(IProgressMonitor) + */ + @Override + public void shutdown(IProgressMonitor monitor) { + // nothing to do here + } + + /** + * @see org.eclipse.core.internal.resources.IManager#startup(IProgressMonitor) + */ + @Override + public void startup(IProgressMonitor monitor) { + // nothing to do here + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#validateName(String) + */ + @Override + public IStatus validateName(String name) { + String message = null; + if (name.length() == 0) { + message = Messages.pathvar_length; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + char first = name.charAt(0); + if (!Character.isLetter(first) && first != '_') { + message = NLS.bind(Messages.pathvar_beginLetter, String.valueOf(first)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + for (int i = 1; i < name.length(); i++) { + char following = name.charAt(i); + if (Character.isWhitespace(following)) + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, Messages.pathvar_whitespace); + if (!Character.isLetter(following) && !Character.isDigit(following) && following != '_') { + message = NLS.bind(Messages.pathvar_invalidChar, String.valueOf(following)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + } + // check + + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#validateValue(IPath) + */ + @Override + public IStatus validateValue(IPath value) { + // accept any format + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#validateValue(URI) + */ + @Override + public IStatus validateValue(URI value) { + // accept any format + return Status.OK_STATUS; + } + + /** + * @throws CoreException + * @see IPathVariableManager#convertToRelative(URI, boolean, String) + */ + @Override + public URI convertToRelative(URI path, boolean force, String variableHint) throws CoreException { + return PathVariableUtil.convertToRelative(this, path, resource, force, variableHint); + } + + /** + * @see IPathVariableManager#convertToUserEditableFormat(String, boolean) + */ + @Override + public String convertToUserEditableFormat(String value, boolean locationFormat) { + return PathVariableUtil.convertToUserEditableFormatInternal(value, locationFormat); + } + + @Override + public String convertFromUserEditableFormat(String userFormat, boolean locationFormat) { + return PathVariableUtil.convertFromUserEditableFormatInternal(this, userFormat, locationFormat); + } + + @Override + public void addChangeListener(IPathVariableChangeListener listener) { + getWorkspaceManager().addChangeListener(listener, resource.getProject()); + } + + @Override + public void removeChangeListener(IPathVariableChangeListener listener) { + getWorkspaceManager().removeChangeListener(listener, resource.getProject()); + } + + /* + * (non-Javadoc) + * + * @see IPathVariableManager#getVariableRelativePathLocation(IResource, URI) + */ + @Override + public URI getVariableRelativePathLocation(URI location) { + try { + URI result = convertToRelative(location, false, null); + if (!result.equals(location)) + return result; + } catch (CoreException e) { + // handled by returning null + } + return null; + } + + /* + * Return the resource of this manager. + */ + public IResource getResource() { + return resource; + } + + @Override + public boolean isUserDefined(String name) { + return ProjectVariableProviderManager.getDefault().findDescriptor(name) == null; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java new file mode 100644 index 0000000000..002d1d5fe7 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java @@ -0,0 +1,691 @@ +/******************************************************************************* + * 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 + * Markus Schorn (Wind River) - [108066] Project prefs marked dirty on read + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.*; +import org.eclipse.core.internal.preferences.EclipsePreferences; +import org.eclipse.core.internal.preferences.ExportedPreferences; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IExportedPreferences; +import org.eclipse.osgi.util.NLS; +import org.osgi.service.prefs.BackingStoreException; +import org.osgi.service.prefs.Preferences; + +/** + * Represents a node in the Eclipse preference hierarchy which stores preference + * values for projects. + * + * @since 3.0 + */ +public class ProjectPreferences extends EclipsePreferences { + static final String PREFS_REGULAR_QUALIFIER = ResourcesPlugin.PI_RESOURCES; + static final String PREFS_DERIVED_QUALIFIER = PREFS_REGULAR_QUALIFIER + ".derived"; //$NON-NLS-1$ + /** + * Cache which nodes have been loaded from disk + */ + protected static Set loadedNodes = Collections.synchronizedSet(new HashSet()); + private IFile file; + private boolean initialized = false; + /** + * Flag indicating that this node is currently reading values from disk, + * to avoid flushing during a read. + */ + private boolean isReading; + /** + * Flag indicating that this node is currently writing values to disk, + * to avoid re-reading after the write completes. + */ + private boolean isWriting; + private IEclipsePreferences loadLevel; + private IProject project; + private String qualifier; + + // cache + private int segmentCount; + + static void deleted(IFile file) throws CoreException { + IPath path = file.getFullPath(); + int count = path.segmentCount(); + if (count != 3) + return; + // check if we are in the .settings directory + if (!EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME.equals(path.segment(1))) + return; + Preferences root = Platform.getPreferencesService().getRootNode(); + String project = path.segment(0); + String qualifier = path.removeFileExtension().lastSegment(); + ProjectPreferences projectNode = (ProjectPreferences) root.node(ProjectScope.SCOPE).node(project); + // if the node isn't known then just return + try { + if (!projectNode.nodeExists(qualifier)) + return; + } catch (BackingStoreException e) { + // ignore + } + + // clear the preferences + clearNode(projectNode.node(qualifier)); + + // notifies the CharsetManager if needed + if (qualifier.equals(PREFS_REGULAR_QUALIFIER) || qualifier.equals(PREFS_DERIVED_QUALIFIER)) + preferencesChanged(file.getProject()); + } + + static void deleted(IFolder folder) throws CoreException { + IPath path = folder.getFullPath(); + int count = path.segmentCount(); + if (count != 2) + return; + // check if we are the .settings directory + if (!EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME.equals(path.segment(1))) + return; + Preferences root = Platform.getPreferencesService().getRootNode(); + // The settings dir has been removed/moved so remove all project prefs + // for the resource. + String project = path.segment(0); + Preferences projectNode = root.node(ProjectScope.SCOPE).node(project); + // check if we need to notify the charset manager + boolean hasResourcesSettings = getFile(folder, PREFS_REGULAR_QUALIFIER).exists() || getFile(folder, PREFS_DERIVED_QUALIFIER).exists(); + // remove the preferences + removeNode(projectNode); + // notifies the CharsetManager + if (hasResourcesSettings) + preferencesChanged(folder.getProject()); + } + + /* + * The whole project has been removed so delete all of the project settings + */ + static void deleted(IProject project) throws CoreException { + // The settings dir has been removed/moved so remove all project prefs + // for the resource. We have to do this now because (since we aren't + // synchronizing) there is short-circuit code that doesn't visit the + // children. + Preferences root = Platform.getPreferencesService().getRootNode(); + Preferences projectNode = root.node(ProjectScope.SCOPE).node(project.getName()); + // check if we need to notify the charset manager + boolean hasResourcesSettings = getFile(project, PREFS_REGULAR_QUALIFIER).exists() || getFile(project, PREFS_DERIVED_QUALIFIER).exists(); + // remove the preferences + removeNode(projectNode); + // notifies the CharsetManager + if (hasResourcesSettings) + preferencesChanged(project); + } + + static void deleted(IResource resource) throws CoreException { + switch (resource.getType()) { + case IResource.FILE : + deleted((IFile) resource); + return; + case IResource.FOLDER : + deleted((IFolder) resource); + return; + case IResource.PROJECT : + deleted((IProject) resource); + return; + } + } + + /* + * Return the preferences file for the given folder and qualifier. + */ + static IFile getFile(IFolder folder, String qualifier) { + Assert.isLegal(folder.getName().equals(DEFAULT_PREFERENCES_DIRNAME)); + return folder.getFile(new Path(qualifier).addFileExtension(PREFS_FILE_EXTENSION)); + } + + /* + * Return the preferences file for the given project and qualifier. + */ + static IFile getFile(IProject project, String qualifier) { + return project.getFile(new Path(DEFAULT_PREFERENCES_DIRNAME).append(qualifier).addFileExtension(PREFS_FILE_EXTENSION)); + } + + private static Properties loadProperties(IFile file) throws BackingStoreException { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Loading preferences from file: " + file.getFullPath()); //$NON-NLS-1$ + Properties result = new Properties(); + InputStream input = null; + try { + input = new BufferedInputStream(file.getContents(true)); + result.load(input); + } catch (CoreException e) { + String message = NLS.bind(Messages.preferences_loadException, file.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } catch (IOException e) { + String message = NLS.bind(Messages.preferences_loadException, file.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } finally { + FileUtil.safeClose(input); + } + return result; + } + + private static void preferencesChanged(IProject project) { + Workspace workspace = ((Workspace) ResourcesPlugin.getWorkspace()); + workspace.getCharsetManager().projectPreferencesChanged(project); + workspace.getContentDescriptionManager().projectPreferencesChanged(project); + } + + private static void read(ProjectPreferences node, IFile file) throws BackingStoreException, CoreException { + if (file == null || !file.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Unable to determine preference file or file does not exist for node: " + node.absolutePath()); //$NON-NLS-1$ + return; + } + Properties fromDisk = loadProperties(file); + // no work to do + if (fromDisk.isEmpty()) + return; + // create a new node to store the preferences in. + IExportedPreferences myNode = (IExportedPreferences) ExportedPreferences.newRoot().node(node.absolutePath()); + convertFromProperties((EclipsePreferences) myNode, fromDisk, false); + //flag that we are currently reading, to avoid unnecessary writing + boolean oldIsReading = node.isReading; + node.isReading = true; + try { + Platform.getPreferencesService().applyPreferences(myNode); + } finally { + node.isReading = oldIsReading; + } + } + + static void removeNode(Preferences node) throws CoreException { + String message = NLS.bind(Messages.preferences_removeNodeException, node.absolutePath()); + try { + node.removeNode(); + } catch (BackingStoreException e) { + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e); + throw new CoreException(status); + } + removeLoadedNodes(node); + } + + static void clearNode(Preferences node) throws CoreException { + // if the underlying properties file was deleted, clear the values and remove + // it from the list of loaded nodes, keep the node as it might still be referenced + try { + clearAll(node); + } catch (BackingStoreException e) { + String message = NLS.bind(Messages.preferences_clearNodeException, node.absolutePath()); + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e); + throw new CoreException(status); + } + removeLoadedNodes(node); + } + + private static void clearAll(Preferences node) throws BackingStoreException { + node.clear(); + String[] names = node.childrenNames(); + for (int i = 0; i < names.length; i++) { + clearAll(node.node(names[i])); + } + } + + private static void removeLoadedNodes(Preferences node) { + String path = node.absolutePath(); + synchronized (loadedNodes) { + for (Iterator i = loadedNodes.iterator(); i.hasNext();) { + String key = i.next(); + if (key.startsWith(path)) + i.remove(); + } + } + } + + public static void updatePreferences(IFile file) throws CoreException { + IPath path = file.getFullPath(); + // if we made it this far we are inside /project/.settings and might + // have a change to a preference file + if (!PREFS_FILE_EXTENSION.equals(path.getFileExtension())) + return; + + String project = path.segment(0); + String qualifier = path.removeFileExtension().lastSegment(); + Preferences root = Platform.getPreferencesService().getRootNode(); + Preferences node = root.node(ProjectScope.SCOPE).node(project).node(qualifier); + String message = null; + try { + message = NLS.bind(Messages.preferences_syncException, node.absolutePath()); + if (!(node instanceof ProjectPreferences)) + return; + ProjectPreferences projectPrefs = (ProjectPreferences) node; + if (projectPrefs.isWriting) + return; + read(projectPrefs, file); + // Bug 108066: In case the node had existed before it was updated from + // file, the read() operation marks it dirty. Override the dirty flag + // since we know that the node is expected to be in sync with the file. + projectPrefs.dirty = false; + + // make sure that we generate the appropriate resource change events + // if encoding settings have changed + if (PREFS_REGULAR_QUALIFIER.equals(qualifier) || PREFS_DERIVED_QUALIFIER.equals(qualifier)) + preferencesChanged(file.getProject()); + } catch (BackingStoreException e) { + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e); + throw new CoreException(status); + } + } + + /** + * Default constructor. Should only be called by #createExecutableExtension. + */ + public ProjectPreferences() { + super(null, null); + } + + private ProjectPreferences(EclipsePreferences parent, String name) { + super(parent, name); + + // cache the segment count + String path = absolutePath(); + segmentCount = getSegmentCount(path); + + if (segmentCount == 1) + return; + + // cache the project name + String projectName = getSegment(path, 1); + if (projectName != null) + project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); + + // cache the qualifier + if (segmentCount > 2) + qualifier = getSegment(path, 2); + } + + @Override + public String[] childrenNames() throws BackingStoreException { + // illegal state if this node has been removed + checkRemoved(); + initialize(); + silentLoad(); + return super.childrenNames(); + } + + @Override + public void clear() { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + super.clear(); + } + + /* + * Figure out what the children of this node are based on the resources + * that are in the workspace. + */ + private String[] computeChildren() { + if (project == null) + return EMPTY_STRING_ARRAY; + IFolder folder = project.getFolder(DEFAULT_PREFERENCES_DIRNAME); + if (!folder.exists()) + return EMPTY_STRING_ARRAY; + IResource[] members = null; + try { + members = folder.members(); + } catch (CoreException e) { + return EMPTY_STRING_ARRAY; + } + ArrayList result = new ArrayList(); + for (int i = 0; i < members.length; i++) { + IResource resource = members[i]; + if (resource.getType() == IResource.FILE && PREFS_FILE_EXTENSION.equals(resource.getFullPath().getFileExtension())) + result.add(resource.getFullPath().removeFileExtension().lastSegment()); + } + return result.toArray(EMPTY_STRING_ARRAY); + } + + @Override + public void flush() throws BackingStoreException { + if (isReading) + return; + isWriting = true; + try { + // call the internal method because we don't want to be synchronized, we will do that ourselves later. + IEclipsePreferences toFlush = super.internalFlush(); + //if we aren't at the right level, then flush the appropriate node + if (toFlush != null) + toFlush.flush(); + } finally { + isWriting = false; + } + } + + private IFile getFile() { + if (file == null) { + if (project == null || qualifier == null) + return null; + file = getFile(project, qualifier); + } + return file; + } + + /* + * Return the node at which these preferences are loaded/saved. + */ + @Override + protected IEclipsePreferences getLoadLevel() { + if (loadLevel == null) { + if (project == null || qualifier == null) + return null; + // Make it relative to this node rather than navigating to it from the root. + // Walk backwards up the tree starting at this node. + // This is important to avoid a chicken/egg thing on startup. + EclipsePreferences node = this; + for (int i = 3; i < segmentCount; i++) + node = (EclipsePreferences) node.parent(); + loadLevel = node; + } + return loadLevel; + } + + /* + * Calculate and return the file system location for this preference node. + * Use the absolute path of the node to find out the project name so + * we can get its location on disk. + * + * NOTE: we cannot cache the location since it may change over the course + * of the project life-cycle. + */ + @Override + protected IPath getLocation() { + if (project == null || qualifier == null) + return null; + IPath path = project.getLocation(); + return computeLocation(path, qualifier); + } + + @Override + protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) { + return new ProjectPreferences(nodeParent, nodeName); + } + + @Override + protected String internalGet(String key) { + // throw NPE if key is null + if (key == null) + throw new NullPointerException(); + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + return super.internalGet(key); + } + + @Override + protected String internalPut(String key, String newValue) { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + if ((segmentCount == 3) && PREFS_REGULAR_QUALIFIER.equals(qualifier) && (project != null)) { + if (ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS.equals(key)) { + CharsetManager charsetManager = ((Workspace) ResourcesPlugin.getWorkspace()).getCharsetManager(); + if (Boolean.parseBoolean(newValue)) + charsetManager.splitEncodingPreferences(project); + else + charsetManager.mergeEncodingPreferences(project); + } + } + return super.internalPut(key, newValue); + } + + private void initialize() { + if (segmentCount != 2) + return; + + // if already initialized, then skip this initialization + if (initialized) + return; + + // initialize the children only if project is opened + if (project.isOpen()) { + try { + synchronized (this) { + List addedNames = Arrays.asList(internalChildNames()); + String[] names = computeChildren(); + // add names only for nodes that were not added previously + for (int i = 0; i < names.length; i++) + if (!addedNames.contains(names[i])) + addChild(names[i], null); + } + } finally { + // mark as initialized so that subsequent project opening will not initialize preferences again + initialized = true; + } + } + } + + @Override + protected boolean isAlreadyLoaded(IEclipsePreferences node) { + return loadedNodes.contains(node.absolutePath()); + } + + @Override + public String[] keys() { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + return super.keys(); + } + + @Override + protected void load() throws BackingStoreException { + load(true); + } + + private void load(boolean reportProblems) throws BackingStoreException { + IFile localFile = getFile(); + if (localFile == null || !localFile.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Unable to determine preference file or file does not exist for node: " + absolutePath()); //$NON-NLS-1$ + return; + } + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Loading preferences from file: " + localFile.getFullPath()); //$NON-NLS-1$ + Properties fromDisk = new Properties(); + InputStream input = null; + try { + input = new BufferedInputStream(localFile.getContents(true)); + fromDisk.load(input); + convertFromProperties(this, fromDisk, true); + loadedNodes.add(absolutePath()); + } catch (CoreException e) { + if (reportProblems) { + String message = NLS.bind(Messages.preferences_loadException, localFile.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } + } catch (IOException e) { + if (reportProblems) { + String message = NLS.bind(Messages.preferences_loadException, localFile.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } + } finally { + FileUtil.safeClose(input); + } + } + + /* (non-Javadoc) + * @see org.eclipse.core.internal.preferences.EclipsePreferences#nodeExists(java.lang.String) + * + * If we are at the /project node and we are checking for the existence of a child, we + * want special behaviour. If the child is a single segment name, then we want to + * return true if the node exists OR if a project with that name exists in the workspace. + */ + @Override + public boolean nodeExists(String path) throws BackingStoreException { + // short circuit for checking this node + if (path.length() == 0) + return !removed; + // illegal state if this node has been removed. + // do this AFTER checking for the empty string. + checkRemoved(); + initialize(); + silentLoad(); + if (segmentCount != 1) + return super.nodeExists(path); + if (path.length() == 0) + return super.nodeExists(path); + if (path.charAt(0) == IPath.SEPARATOR) + return super.nodeExists(path); + if (path.indexOf(IPath.SEPARATOR) != -1) + return super.nodeExists(path); + // if we are checking existance of a single segment child of /project, base the answer on + // whether or not it exists in the workspace. + return ResourcesPlugin.getWorkspace().getRoot().getProject(path).exists() || super.nodeExists(path); + } + + @Override + public void remove(String key) { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + super.remove(key); + if ((segmentCount == 3) && PREFS_REGULAR_QUALIFIER.equals(qualifier) && (project != null)) { + if (ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS.equals(key)) { + CharsetManager charsetManager = ((Workspace) ResourcesPlugin.getWorkspace()).getCharsetManager(); + if (ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS) + charsetManager.splitEncodingPreferences(project); + else + charsetManager.mergeEncodingPreferences(project); + } + } + } + + @Override + protected void save() throws BackingStoreException { + final IFile fileInWorkspace = getFile(); + if (fileInWorkspace == null) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Not saving preferences since there is no file for node: " + absolutePath()); //$NON-NLS-1$ + return; + } + final String finalQualifier = qualifier; + final BackingStoreException[] bse = new BackingStoreException[1]; + try { + IWorkspaceRunnable operation = new IWorkspaceRunnable() { + @Override + public void run(IProgressMonitor monitor) throws CoreException { + try { + Properties table = convertToProperties(new SortedProperties(), ""); //$NON-NLS-1$ + // nothing to save. delete existing file if one exists. + if (table.isEmpty()) { + if (fileInWorkspace.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Deleting preference file: " + fileInWorkspace.getFullPath()); //$NON-NLS-1$ + if (fileInWorkspace.isReadOnly()) { + IStatus status = fileInWorkspace.getWorkspace().validateEdit(new IFile[] {fileInWorkspace}, IWorkspace.VALIDATE_PROMPT); + if (!status.isOK()) + throw new CoreException(status); + } + try { + fileInWorkspace.delete(true, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.preferences_deleteException, fileInWorkspace.getFullPath()); + log(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IStatus.WARNING, message, null)); + } + } + return; + } + table.put(VERSION_KEY, VERSION_VALUE); + // print the table to a string and remove the timestamp that Properties#store always adds + String s = removeTimestampFromTable(table); + String systemLineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$ + String fileLineSeparator = FileUtil.getLineSeparator(fileInWorkspace); + if (!systemLineSeparator.equals(fileLineSeparator)) + s = s.replaceAll(systemLineSeparator, fileLineSeparator); + InputStream input = new BufferedInputStream(new ByteArrayInputStream(s.getBytes("UTF-8"))); //$NON-NLS-1$ + if (fileInWorkspace.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Setting preference file contents for: " + fileInWorkspace.getFullPath()); //$NON-NLS-1$ + if (fileInWorkspace.isReadOnly()) { + IStatus status = fileInWorkspace.getWorkspace().validateEdit(new IFile[] {fileInWorkspace}, IWorkspace.VALIDATE_PROMPT); + if (!status.isOK()) { + input.close(); + throw new CoreException(status); + } + } + // set the contents + fileInWorkspace.setContents(input, IResource.KEEP_HISTORY, null); + } else { + // create the file + IFolder folder = (IFolder) fileInWorkspace.getParent(); + if (!folder.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Creating parent preference directory: " + folder.getFullPath()); //$NON-NLS-1$ + folder.create(IResource.NONE, true, null); + } + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Creating preference file: " + fileInWorkspace.getLocation()); //$NON-NLS-1$ + fileInWorkspace.create(input, IResource.NONE, null); + } + if (PREFS_DERIVED_QUALIFIER.equals(finalQualifier)) + fileInWorkspace.setDerived(true, null); + } catch (BackingStoreException e) { + bse[0] = e; + } catch (IOException e) { + String message = NLS.bind(Messages.preferences_saveProblems, fileInWorkspace.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + bse[0] = new BackingStoreException(message); + } + } + }; + //don't bother with scheduling rules if we are already inside an operation + try { + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + if (((Workspace) workspace).getWorkManager().isLockAlreadyAcquired()) { + operation.run(null); + } else { + IResourceRuleFactory factory = workspace.getRuleFactory(); + // we might: delete the file, create the .settings folder, create the file, modify the file, or set derived flag for the file. + ISchedulingRule rule = MultiRule.combine(new ISchedulingRule[] {factory.deleteRule(fileInWorkspace), factory.createRule(fileInWorkspace.getParent()), factory.modifyRule(fileInWorkspace), factory.derivedRule(fileInWorkspace)}); + workspace.run(operation, rule, IResource.NONE, null); + if (bse[0] != null) + throw bse[0]; + } + } catch (OperationCanceledException e) { + throw new BackingStoreException(Messages.preferences_operationCanceled); + } + } catch (CoreException e) { + String message = NLS.bind(Messages.preferences_saveProblems, fileInWorkspace.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } + } + + private void silentLoad() { + ProjectPreferences node = (ProjectPreferences) getLoadLevel(); + if (node == null) + return; + if (isAlreadyLoaded(node) || node.isLoading()) + return; + try { + node.setLoading(true); + node.load(false); + } catch (BackingStoreException e) { + // will not happen, all exceptions are swallowed by load(false) + } finally { + node.setLoading(false); + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java new file mode 100644 index 0000000000..5daf1adc98 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * 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 java.util.Map; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * Repository for all variable providers available through the extension points. + * @since 3.6 + */ +public class ProjectVariableProviderManager { + + public static class Descriptor { + PathVariableResolver provider = null; + String name = null; + String value = null; + + public Descriptor(IExtension extension, IConfigurationElement element) throws RuntimeException, CoreException { + name = element.getAttribute("variable"); //$NON-NLS-1$ + value = element.getAttribute("value"); //$NON-NLS-1$ + try { + String classAttribute = "class"; //$NON-NLS-1$ + if (element.getAttribute(classAttribute) != null) + provider = (PathVariableResolver) element.createExecutableExtension(classAttribute); + } catch (CoreException e) { + Policy.log(e); + } + if (name == null) + fail(NLS.bind(Messages.mapping_invalidDef, extension.getUniqueIdentifier())); + } + + protected void fail(String reason) throws CoreException { + throw new ResourceException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, reason, null)); + } + + public String getName() { + return name; + } + + public String getValue(String variable, IResource resource) { + if (value != null) + return value; + return provider.getValue(variable, resource); + } + + public String[] getVariableNames(String variable, IResource resource) { + if (provider != null) + return provider.getVariableNames(variable, resource); + if (name.equals(variable)) + return new String[] {variable}; + return null; + } + } + + private static Map descriptors; + private static Descriptor[] descriptorsArray; + private static ProjectVariableProviderManager instance = new ProjectVariableProviderManager(); + + public static ProjectVariableProviderManager getDefault() { + return instance; + } + + public Descriptor[] getDescriptors() { + lazyInitialize(); + return descriptorsArray; + } + + protected void lazyInitialize() { + if (descriptors != null) + return; + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_VARIABLE_PROVIDERS); + IExtension[] extensions = point.getExtensions(); + descriptors = new HashMap(extensions.length * 2 + 1); + for (int i = 0, imax = extensions.length; i < imax; i++) { + IConfigurationElement[] elements = extensions[i].getConfigurationElements(); + int count = elements.length; + for (int j = 0; j < count; j++) { + IConfigurationElement element = elements[j]; + String elementName = element.getName(); + if (elementName.equalsIgnoreCase("variableResolver")) { //$NON-NLS-1$ + Descriptor desc = null; + try { + desc = new Descriptor(extensions[i], element); + } catch (CoreException e) { + Policy.log(e); + } + if (desc != null) + descriptors.put(desc.getName(), desc); + } + } + } + descriptorsArray = descriptors.values().toArray(new Descriptor[descriptors.size()]); + } + + public Descriptor findDescriptor(String name) { + lazyInitialize(); + Descriptor result = descriptors.get(name); + return result; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java new file mode 100644 index 0000000000..ba29773e39 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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 development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.regex.*; +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.filtermatchers.AbstractFileInfoMatcher; +import org.eclipse.core.runtime.*; + +/** + * A Filter provider for Java Regular expression supported by + * java.util.regex.Pattern. + */ +public class RegexFileInfoMatcher extends AbstractFileInfoMatcher { + + Pattern pattern = null; + + public RegexFileInfoMatcher() { + // nothing to do + } + + @Override + public boolean matches(IContainer parent, IFileInfo fileInfo) { + if (pattern != null) { + Matcher m = pattern.matcher(fileInfo.getName()); + return m.matches(); + } + return false; + } + + @Override + public void initialize(IProject project, Object arguments) throws CoreException { + if (arguments != null) { + try { + pattern = Pattern.compile((String) arguments); + } catch (PatternSyntaxException e) { + throw new CoreException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, Platform.PLUGIN_ERROR, e.getMessage(), e)); + } + } + } +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java new file mode 100644 index 0000000000..c1eff45478 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java @@ -0,0 +1,2083 @@ +/******************************************************************************* + * 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 + * Dan Rubel - Implementation of getLocalTimeStamp + * Red Hat Incorporated - get/setResourceAttribute code + * Oakland Software Incorporated - added getSessionProperties and getPersistentProperties + * Holger Oehm - [226264] race condition in Workspace.isTreeLocked()/setTreeLocked() + * Martin Oberhuber (Wind River) - [245937] ProjectDescription#setLinkLocation() detects non-change + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support + * James Blackburn (Broadcom Corp.) - ongoing development + * Sergey Prigogin (Google) - [338010] Resource.createLink() does not preserve symbolic links + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.internal.events.LifecycleEvent; +import org.eclipse.core.internal.localstore.FileSystemResourceManager; +import org.eclipse.core.internal.properties.IPropertyManager; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.internal.watson.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IMoveDeleteHook; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; +import org.eclipse.osgi.util.NLS; + +public abstract class Resource extends PlatformObject implements IResource, ICoreConstants, Cloneable, IPathRequestor { + /* package */IPath path; + /* package */Workspace workspace; + + protected Resource(IPath path, Workspace workspace) { + this.path = path.removeTrailingSeparator(); + this.workspace = workspace; + } + + @Override + public void accept(IResourceProxyVisitor visitor, int memberFlags) throws CoreException { + accept(visitor, IResource.DEPTH_INFINITE, memberFlags); + } + + @Override + public void accept(final IResourceProxyVisitor visitor, final int depth, final int memberFlags) throws CoreException { + // it is invalid to call accept on a phantom when INCLUDE_PHANTOMS is not specified + final boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0; + if ((memberFlags & IContainer.DO_NOT_CHECK_EXISTENCE) == 0) + checkAccessible(getFlags(getResourceInfo(includePhantoms, false))); + + final ResourceProxy proxy = new ResourceProxy(); + IElementContentVisitor elementVisitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object contents) { + ResourceInfo info = (ResourceInfo) contents; + if (!isMember(getFlags(info), memberFlags)) + return false; + proxy.requestor = requestor; + proxy.info = info; + try { + boolean shouldContinue = true; + switch (depth) { + case DEPTH_ZERO : + shouldContinue = false; + break; + case DEPTH_ONE : + shouldContinue = !path.equals(requestor.requestPath().removeLastSegments(1)); + break; + case DEPTH_INFINITE : + shouldContinue = true; + break; + } + return visitor.visit(proxy) && shouldContinue; + } catch (CoreException e) { + //throw an exception to bail out of the traversal + throw new WrappedRuntimeException(e); + } finally { + proxy.reset(); + } + } + }; + try { + new ElementTreeIterator(workspace.getElementTree(), getFullPath()).iterate(elementVisitor); + } catch (WrappedRuntimeException e) { + throw (CoreException) e.getTargetException(); + } finally { + proxy.requestor = null; + proxy.info = null; + } + } + + @Override + public void accept(IResourceVisitor visitor) throws CoreException { + accept(visitor, IResource.DEPTH_INFINITE, 0); + } + + @Override + public void accept(IResourceVisitor visitor, int depth, boolean includePhantoms) throws CoreException { + accept(visitor, depth, includePhantoms ? IContainer.INCLUDE_PHANTOMS : 0); + } + + @Override + public void accept(final IResourceVisitor visitor, int depth, int memberFlags) throws CoreException { + //use the fast visitor if visiting to infinite depth + if (depth == IResource.DEPTH_INFINITE) { + accept(new IResourceProxyVisitor() { + @Override + public boolean visit(IResourceProxy proxy) throws CoreException { + return visitor.visit(proxy.requestResource()); + } + }, memberFlags); + return; + } + // it is invalid to call accept on a phantom when INCLUDE_PHANTOMS is not specified + final boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0; + ResourceInfo info = getResourceInfo(includePhantoms, false); + int flags = getFlags(info); + if ((memberFlags & IContainer.DO_NOT_CHECK_EXISTENCE) == 0) + checkAccessible(flags); + + //check that this resource matches the member flags + if (!isMember(flags, memberFlags)) + return; + // visit this resource + if (!visitor.visit(this) || depth == DEPTH_ZERO) + return; + // get the info again because it might have been changed by the visitor + info = getResourceInfo(includePhantoms, false); + if (info == null) + return; + // thread safety: (cache the type to avoid changes -- we might not be inside an operation) + int type = info.getType(); + if (type == FILE) + return; + // if we had a gender change we need to fix up the resource before asking for its members + IContainer resource = getType() != type ? (IContainer) workspace.newResource(getFullPath(), type) : (IContainer) this; + IResource[] members = resource.members(memberFlags); + for (int i = 0; i < members.length; i++) + members[i].accept(visitor, DEPTH_ZERO, memberFlags | IContainer.DO_NOT_CHECK_EXISTENCE); + } + + protected void assertCopyRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + IStatus status = checkCopyRequirements(destination, destinationType, updateFlags); + if (!status.isOK()) { + // this assert is ok because the error cases generated by the + // check method above indicate assertion conditions. + Assert.isTrue(false, status.getChildren()[0].getMessage()); + } + } + + /** + * Throws an exception if the link preconditions are not met. Returns the file info + * for the file being linked to, or null if not available. + * @throws CoreException + */ + protected IFileInfo assertLinkRequirements(URI localLocation, int updateFlags) throws CoreException { + boolean allowMissingLocal = (updateFlags & IResource.ALLOW_MISSING_LOCAL) != 0; + if ((updateFlags & IResource.REPLACE) == 0) + checkDoesNotExist(getFlags(getResourceInfo(false, false)), true); + IStatus locationStatus = workspace.validateLinkLocationURI(this, localLocation); + //we only tolerate an undefined path variable in the allow missing local case + final boolean variableUndefined = locationStatus.getCode() == IResourceStatus.VARIABLE_NOT_DEFINED_WARNING; + if (locationStatus.getSeverity() == IStatus.ERROR || (variableUndefined && !allowMissingLocal)) + throw new ResourceException(locationStatus); + //check that the parent exists and is open + Container parent = (Container) getParent(); + parent.checkAccessible(getFlags(parent.getResourceInfo(false, false))); + //if the variable is undefined we can't do any further checks + if (variableUndefined) + return null; + //check if the file exists + URI resolved = getPathVariableManager().resolveURI(localLocation); + IFileStore store = EFS.getStore(resolved); + IFileInfo fileInfo = store.fetchInfo(); + boolean localExists = fileInfo.exists(); + if (!allowMissingLocal && !localExists) { + String msg = NLS.bind(Messages.links_localDoesNotExist, store.toString()); + throw new ResourceException(IResourceStatus.NOT_FOUND_LOCAL, getFullPath(), msg, null); + } + //resource type and file system type must match + if (localExists && ((getType() == IResource.FOLDER) != fileInfo.isDirectory())) { + String msg = NLS.bind(Messages.links_wrongLocalType, getFullPath()); + throw new ResourceException(IResourceStatus.WRONG_TYPE_LOCAL, getFullPath(), msg, null); + } + return fileInfo; + } + + protected void assertMoveRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + IStatus status = checkMoveRequirements(destination, destinationType, updateFlags); + if (!status.isOK()) { + // this assert is ok because the error cases generated by the + // check method above indicate assertion conditions. + Assert.isTrue(false, status.getChildren()[0].getMessage()); + } + } + + public void checkAccessible(int flags) throws CoreException { + checkExists(flags, true); + } + + private ResourceInfo checkAccessibleAndLocal(int depth) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + checkLocal(flags, depth); + return info; + } + + /** + * This method reports errors in two different ways. It can throw a + * CoreException or return a status. CoreExceptions are used according to the + * specification of the copy method. Programming errors, that would usually be + * prevented by using an "Assert" code, are reported as an IStatus. We're doing + * this way because we have two different methods to copy resources: + * IResource#copy and IWorkspace#copy. The first one gets the error and throws + * its message in an AssertionFailureException. The second one just throws a + * CoreException using the status returned by this method. + * + * @see IResource#copy(IPath, int, IProgressMonitor) + */ + public IStatus checkCopyRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + String message = Messages.resources_copyNotMet; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_VALUE, message, null); + if (destination == null) { + message = Messages.resources_destNotNull; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message); + } + destination = makePathAbsolute(destination); + if (getFullPath().isPrefixOf(destination)) { + message = NLS.bind(Messages.resources_copyDestNotSub, getFullPath()); + status.add(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message)); + } + checkValidPath(destination, destinationType, false); + + ResourceInfo info; + checkAccessibleAndLocal(DEPTH_INFINITE); + + IPath destinationParent = destination.removeLastSegments(1); + checkValidGroupContainer(destinationParent, isLinked(), isVirtual()); + + Resource dest = workspace.newResource(destination, destinationType); + dest.checkDoesNotExist(); + + // ensure we aren't trying to copy a file to a project + if (getType() == IResource.FILE && destinationType == IResource.PROJECT) { + message = Messages.resources_fileToProj; + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + + // we can't copy into a closed project + if (destinationType != IResource.PROJECT) { + Project project = (Project) dest.getProject(); + info = project.getResourceInfo(false, false); + project.checkAccessible(getFlags(info)); + Container parent = (Container) dest.getParent(); + if (!parent.equals(project)) { + info = parent.getResourceInfo(false, false); + parent.checkExists(getFlags(info), true); + } + } + if (isUnderLink() || dest.isUnderLink()) { + //make sure location is not null. This can occur with linked resources relative to + //undefined path variables + URI sourceLocation = getLocationURI(); + if (sourceLocation == null) { + message = NLS.bind(Messages.localstore_locationUndefined, getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, getFullPath(), message, null); + } + URI destLocation = dest.getLocationURI(); + if (destLocation == null && (dest.isUnderVirtual() == false)) { + message = NLS.bind(Messages.localstore_locationUndefined, dest.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, dest.getFullPath(), message, null); + } + //make sure location of source is not a prefix of the location of the destination + //this can occur if the source and/or destination is a linked resource + if (getStore().isParentOf(dest.getStore())) { + message = NLS.bind(Messages.resources_copyDestNotSub, getFullPath()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + } + + return status.isOK() ? Status.OK_STATUS : (IStatus) status; + } + + /** + * Checks that this resource does not exist. If the file system is not case + * sensitive, this method also checks for a case variant. + */ + protected void checkDoesNotExist() throws CoreException { + checkDoesNotExist(getFlags(getResourceInfo(false, false)), false); + } + + /** + * Checks that this resource does not exist. If the file system is not case + * sensitive, this method also checks for a case variant. + * + * @exception CoreException if this resource exists + */ + public void checkDoesNotExist(int flags, boolean checkType) throws CoreException { + //if this exact resource exists we are done + if (exists(flags, checkType)) { + String message = NLS.bind(Messages.resources_mustNotExist, getFullPath()); + throw new ResourceException(checkType ? IResourceStatus.RESOURCE_EXISTS : IResourceStatus.PATH_OCCUPIED, getFullPath(), message, null); + } + if (Workspace.caseSensitive) + return; + //now look for a matching case variant in the tree + IResource variant = findExistingResourceVariant(getFullPath()); + if (variant == null) + return; + String msg = NLS.bind(Messages.resources_existsDifferentCase, variant.getFullPath()); + throw new ResourceException(IResourceStatus.CASE_VARIANT_EXISTS, variant.getFullPath(), msg, null); + } + + /** + * Checks that this resource exists. + * If checkType is true, the type of this resource and the one in the tree must match. + * + * @exception CoreException if this resource does not exist + */ + public void checkExists(int flags, boolean checkType) throws CoreException { + if (!exists(flags, checkType)) { + String message = NLS.bind(Messages.resources_mustExist, getFullPath()); + throw new ResourceException(IResourceStatus.RESOURCE_NOT_FOUND, getFullPath(), message, null); + } + } + + /** + * Checks that this resource is local to the given depth. + * + * @exception CoreException if this resource is not local + */ + public void checkLocal(int flags, int depth) throws CoreException { + if (!isLocal(flags, depth)) { + String message = NLS.bind(Messages.resources_mustBeLocal, getFullPath()); + throw new ResourceException(IResourceStatus.RESOURCE_NOT_LOCAL, getFullPath(), message, null); + } + } + + /** + * This method reports errors in two different ways. It can throw a + * CoreException or log a status. CoreExceptions are used according + * to the specification of the move method. Programming errors, that + * would usually be prevented by using an "Assert" code, are reported as + * an IStatus. + * We're doing this way because we have two different methods to move + * resources: IResource#move and IWorkspace#move. The first one gets + * the error and throws its message in an AssertionFailureException. The + * second one just throws a CoreException using the status returned + * by this method. + * + * @see IResource#move(IPath, int, IProgressMonitor) + */ + protected IStatus checkMoveRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + String message = Messages.resources_moveNotMet; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_VALUE, message, null); + if (destination == null) { + message = Messages.resources_destNotNull; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message); + } + destination = makePathAbsolute(destination); + if (getFullPath().isPrefixOf(destination)) { + message = NLS.bind(Messages.resources_moveDestNotSub, getFullPath()); + status.add(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message)); + } + checkValidPath(destination, destinationType, false); + + ResourceInfo info; + checkAccessibleAndLocal(DEPTH_INFINITE); + + IPath destinationParent = destination.removeLastSegments(1); + checkValidGroupContainer(destinationParent, isLinked(), isVirtual()); + + Resource dest = workspace.newResource(destination, destinationType); + + // check if we are only changing case + IResource variant = Workspace.caseSensitive ? null : findExistingResourceVariant(destination); + if (variant == null || !this.equals(variant)) + dest.checkDoesNotExist(); + + // ensure we aren't trying to move a file to a project + if (getType() == IResource.FILE && dest.getType() == IResource.PROJECT) { + message = Messages.resources_fileToProj; + throw new ResourceException(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message)); + } + + // we can't move into a closed project + if (destinationType != IResource.PROJECT) { + Project project = (Project) dest.getProject(); + info = project.getResourceInfo(false, false); + project.checkAccessible(getFlags(info)); + Container parent = (Container) dest.getParent(); + if (!parent.equals(project)) { + info = parent.getResourceInfo(false, false); + parent.checkExists(getFlags(info), true); + } + } + if (isUnderLink() || dest.isUnderLink()) { + //make sure location is not null. This can occur with linked resources relative to + //undefined path variables + URI sourceLocation = getLocationURI(); + if (sourceLocation == null) { + message = NLS.bind(Messages.localstore_locationUndefined, getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, getFullPath(), message, null); + } + URI destLocation = dest.getLocationURI(); + if (destLocation == null && (dest.isUnderVirtual() == false)) { + message = NLS.bind(Messages.localstore_locationUndefined, dest.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, dest.getFullPath(), message, null); + } + //make sure location of source is not a prefix of the location of the destination + //this can occur if the source and/or destination is a linked resource + if (getStore().isParentOf(dest.getStore())) { + message = NLS.bind(Messages.resources_moveDestNotSub, getFullPath()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + } + + return status.isOK() ? Status.OK_STATUS : (IStatus) status; + } + + /** + * Checks that the supplied path is valid according to Workspace.validatePath(). + * + * @exception CoreException if the path is not valid + */ + public void checkValidPath(IPath toValidate, int type, boolean lastSegmentOnly) throws CoreException { + IStatus result = workspace.locationValidator.validatePath(toValidate, type, lastSegmentOnly); + if (!result.isOK()) + throw new ResourceException(result); + } + + /** + * Checks that the destination is a suitable one given that it could be a + * group. + * + * @exception CoreException + * if the path points to a group + */ + public void checkValidGroupContainer(IPath destination, boolean isLink, boolean isGroup) throws CoreException { + if (!isLink && !isGroup) { + String message = Messages.group_invalidParent; + ResourceInfo info = workspace.getResourceInfo(destination, false, false); + if (info != null && info.isSet(M_VIRTUAL)) + throw new ResourceException(new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message)); + } + } + + /** + * Checks that the destination is a suitable one given that it could be a + * group. + * + * @exception CoreException + * if the path points to a group + */ + public void checkValidGroupContainer(Container destination, boolean isLink, boolean isGroup) throws CoreException { + if (!isLink && !isGroup) { + String message = Messages.group_invalidParent; + if (destination.isVirtual()) + throw new ResourceException(new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message)); + } + } + + public IStatus getValidGroupContainer(IPath destination, boolean isLink, boolean isGroup) { + if (!isLink && !isGroup) { + String message = Messages.group_invalidParent; + ResourceInfo info = workspace.getResourceInfo(destination, false, false); + if (info.isSet(M_VIRTUAL)) + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + return Status.OK_STATUS; + } + + @Override + public void clearHistory(IProgressMonitor monitor) { + getLocalManager().getHistoryStore().remove(getFullPath(), monitor); + } + + @Override + public boolean contains(ISchedulingRule rule) { + if (this == rule) + return true; + //must allow notifications to nest in all resource rules + if (rule.getClass().equals(WorkManager.NotifyRule.class)) + return true; + if (rule instanceof MultiRule) { + MultiRule multi = (MultiRule) rule; + ISchedulingRule[] children = multi.getChildren(); + for (int i = 0; i < children.length; i++) + if (!contains(children[i])) + return false; + return true; + } + if (!(rule instanceof IResource)) + return false; + IResource resource = (IResource) rule; + if (!workspace.equals(resource.getWorkspace())) + return false; + return path.isPrefixOf(resource.getFullPath()); + } + + /** + * @throws CoreException + */ + public void convertToPhantom() throws CoreException { + ResourceInfo info = getResourceInfo(false, true); + if (info == null || isPhantom(getFlags(info))) + return; + info.clearSessionProperties(); + info.set(M_PHANTOM); + getLocalManager().updateLocalSync(info, I_NULL_SYNC_INFO); + info.clearModificationStamp(); + // should already be done by the #deleteResource call but left in + // just to be safe and for code clarity. + info.setMarkers(null); + } + + @Override + public void copy(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + copy(destination, updateFlags, monitor); + } + + @Override + public void copy(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + try { + monitor = Policy.monitorFor(monitor); + String message = NLS.bind(Messages.resources_copying, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + Policy.checkCanceled(monitor); + destination = makePathAbsolute(destination); + checkValidPath(destination, getType(), false); + Resource destResource = workspace.newResource(destination, getType()); + final ISchedulingRule rule = workspace.getRuleFactory().copyRule(this, destResource); + try { + workspace.prepareOperation(rule, monitor); + // The following assert method throws CoreExceptions as stated in the IResource.copy API + // and assert for programming errors. See checkCopyRequirements for more information. + assertCopyRequirements(destination, getType(), updateFlags); + workspace.beginOperation(true); + getLocalManager().copy(this, destResource, updateFlags, 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(); + } + } + + @Override + public void copy(IProjectDescription destDesc, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + copy(destDesc, updateFlags, monitor); + } + + /* (non-Javadoc) + * Used when a folder is to be copied to a project. + * @see IResource#copy(IProjectDescription, int, IProgressMonitor) + */ + @Override + public void copy(IProjectDescription destDesc, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(destDesc); + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_copying, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + try { + workspace.prepareOperation(workspace.getRoot(), monitor); + // The following assert method throws CoreExceptions as stated in the IResource.copy API + // and assert for programming errors. See checkCopyRequirements for more information. + IPath destPath = new Path(destDesc.getName()).makeAbsolute(); + assertCopyRequirements(destPath, getType(), updateFlags); + Project destProject = (Project) workspace.getRoot().getProject(destPath.lastSegment()); + workspace.beginOperation(true); + + // create and open the new project + destProject.create(destDesc, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + destProject.open(Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + + // copy the children + // FIXME: fix the progress monitor here...create a sub monitor and do a worked(1) after each child instead + IResource[] children = ((IContainer) this).members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < children.length; i++) { + Resource child = (Resource) children[i]; + child.copy(destPath.append(child.getName()), updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 60 / 100 / children.length)); + } + + // copy over the properties + getPropertyManager().copy(this, destProject, DEPTH_ZERO); + monitor.worked(Policy.opWork * 15 / 100); + + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(workspace.getRoot(), true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + /** + * Count the number of resources in the tree from this container to the + * specified depth. Include this resource. Include phantoms if + * the phantom boolean is true. + */ + public int countResources(int depth, boolean phantom) { + return workspace.countResources(path, depth, phantom); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IFolder#createLink(IPath, int, IProgressMonitor) + * @see org.eclipse.core.resources.IFile#createLink(IPath, int, IProgressMonitor) + */ + public void createLink(IPath localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(localLocation); + createLink(URIUtil.toURI(localLocation), updateFlags, monitor); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IFolder#createLink(URI, int, IProgressMonitor) + * @see org.eclipse.core.resources.IFile#createLink(URI, int, IProgressMonitor) + */ + public void createLink(URI localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(localLocation); + monitor = Policy.monitorFor(monitor); + IResource existing = null; + if ((updateFlags & REPLACE) != 0) { + existing = workspace.getRoot().findMember(getFullPath()); + if (existing != null && existing.isLinked()) { + setLinkLocation(localLocation, updateFlags, monitor); + return; + } + } + try { + String message = NLS.bind(Messages.links_creating, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + Policy.checkCanceled(monitor); + checkValidPath(path, FOLDER, true); + final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); + try { + workspace.prepareOperation(rule, monitor); + IFileInfo fileInfo = assertLinkRequirements(localLocation, updateFlags); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_CREATE, this)); + workspace.beginOperation(true); + //replace existing resource, if applicable + if ((updateFlags & REPLACE) != 0) { + if (existing != null) + workspace.deleteResource(existing); + } + ResourceInfo info = workspace.createResource(this, false); + if ((updateFlags & IResource.HIDDEN) != 0) + info.set(M_HIDDEN); + info.set(M_LINK); + LinkDescription linkDescription = new LinkDescription(this, localLocation); + if (linkDescription.isGroup()) + info.set(M_VIRTUAL); + getLocalManager().link(this, localLocation, fileInfo); + monitor.worked(Policy.opWork * 5 / 100); + //save the location in the project description + Project project = (Project) getProject(); + boolean changed = project.internalGetDescription().setLinkLocation(getProjectRelativePath(), linkDescription); + if (changed) + try { + project.writeDescription(IResource.NONE); + } catch (CoreException e) { + // a problem happened updating the description, so delete the resource from the workspace + workspace.deleteResource(this); + throw e; // rethrow + } + 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 IMarker createMarker(String type) throws CoreException { + Assert.isNotNull(type); + final ISchedulingRule rule = workspace.getRuleFactory().markerRule(this); + try { + workspace.prepareOperation(rule, null); + checkAccessible(getFlags(getResourceInfo(false, false))); + workspace.beginOperation(true); + MarkerInfo info = new MarkerInfo(); + info.setType(type); + info.setCreationTime(System.currentTimeMillis()); + workspace.getMarkerManager().add(this, info); + return new Marker(this, info.getId()); + } finally { + workspace.endOperation(rule, false, null); + } + } + + @Override + public IResourceProxy createProxy() { + ResourceProxy result = new ResourceProxy(); + result.info = getResourceInfo(false, false); + result.requestor = this; + result.resource = this; + return result; + } + + /* (non-Javadoc) + * @see IProject#delete(boolean, boolean, IProgressMonitor) + * @see IWorkspaceRoot#delete(boolean, boolean, IProgressMonitor) + * N.B. This is not an IResource method! + */ + public void delete(boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; + delete(updateFlags, monitor); + } + + @Override + public void delete(boolean force, IProgressMonitor monitor) throws CoreException { + delete(force ? IResource.FORCE : IResource.NONE, monitor); + } + + @Override + public void delete(int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_deleting, getFullPath()); + monitor.beginTask("", Policy.totalWork * 1000); //$NON-NLS-1$ + monitor.subTask(message); + final ISchedulingRule rule = workspace.getRuleFactory().deleteRule(this); + try { + workspace.prepareOperation(rule, monitor); + // if there is no resource then there is nothing to delete so just return + if (!exists()) + return; + workspace.beginOperation(true); + broadcastPreDeleteEvent(); + + // when a project is being deleted, flush the build order in case there is a problem + if (this.getType() == IResource.PROJECT) + workspace.flushBuildOrder(); + + final IFileStore originalStore = getStore(); + boolean wasLinked = isLinked(); + message = Messages.resources_deleteProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_DELETE_LOCAL, message, null); + WorkManager workManager = workspace.getWorkManager(); + ResourceTree tree = new ResourceTree(workspace.getFileSystemManager(), workManager.getLock(), status, updateFlags); + int depth = 0; + try { + depth = workManager.beginUnprotected(); + unprotectedDelete(tree, updateFlags, monitor); + } finally { + workManager.endUnprotected(depth); + } + if (getType() == ROOT) { + // need to clear out the root info + workspace.getMarkerManager().removeMarkers(this, IResource.DEPTH_ZERO); + getPropertyManager().deleteProperties(this, IResource.DEPTH_ZERO); + getResourceInfo(false, false).clearSessionProperties(); + } + // Invalidate the tree for further use by clients. + tree.makeInvalid(); + if (!tree.getStatus().isOK()) + throw new ResourceException(tree.getStatus()); + //update any aliases of this resource + //note that deletion of a linked resource cannot affect other resources + if (!wasLinked) + workspace.getAliasManager().updateAliases(this, originalStore, IResource.DEPTH_INFINITE, monitor); + if (getType() == PROJECT) { + // make sure the rule factory is cleared on project deletion + ((Rules) workspace.getRuleFactory()).setRuleFactory((IProject) this, null); + // make sure project deletion is remembered + workspace.getSaveManager().requestSnapshot(); + } + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork * 1000)); + } + } finally { + monitor.done(); + } + } + + @Override + public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException { + final ISchedulingRule rule = workspace.getRuleFactory().markerRule(this); + try { + workspace.prepareOperation(rule, null); + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + + workspace.beginOperation(true); + workspace.getMarkerManager().removeMarkers(this, type, includeSubtypes, depth); + } finally { + workspace.endOperation(rule, false, null); + } + } + + /** + * This method should be called to delete a resource from the tree because it will also + * delete its properties and markers. If a status object is provided, minor exceptions are + * added, otherwise they are thrown. If major exceptions occur, they are always thrown. + */ + public void deleteResource(boolean convertToPhantom, MultiStatus status) throws CoreException { + // remove markers on this resource and its descendents + if (exists()) + getMarkerManager().removeMarkers(this, IResource.DEPTH_INFINITE); + // if this is a linked resource or contains linked resources , remove their entries from the project description + List links = findLinks(); + //pre-delete notification to internal infrastructure + if (links != null) + for (Iterator it = links.iterator(); it.hasNext();) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_DELETE, it.next())); + + // check if we deleted a preferences file + ProjectPreferences.deleted(this); + + //remove all deleted linked resources from the project description + if (getType() != IResource.PROJECT && links != null) { + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + if (description != null) { + boolean wasChanged = false; + for (Iterator it = links.iterator(); it.hasNext();) + wasChanged |= description.setLinkLocation(it.next().getProjectRelativePath(), null); + if (wasChanged) { + project.internalSetDescription(description, true); + try { + project.writeDescription(IResource.FORCE); + } catch (CoreException e) { + // a problem happened updating the description, update the description in memory + project.updateDescription(); + throw e; // rethrow + } + } + } + } + + /* if we are synchronizing, do not delete the resource. Convert it + into a phantom. Actual deletion will happen when we refresh or push. */ + if (convertToPhantom && getType() != PROJECT && synchronizing(getResourceInfo(true, false))) + convertToPhantom(); + else + workspace.deleteResource(this); + + List filters = findFilters(); + if ((filters != null) && (filters.size() > 0)) { + // delete resource filters + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + if (description != null) { + for (Iterator it = filters.iterator(); it.hasNext();) + description.setFilters(it.next().getProjectRelativePath(), null); + project.internalSetDescription(description, true); + project.writeDescription(IResource.FORCE); + } + } + + // Delete properties after the resource is deleted from the tree. See bug 84584. + CoreException err = null; + try { + getPropertyManager().deleteResource(this); + } catch (CoreException e) { + if (status != null) + status.add(e.getStatus()); + else + err = e; + } + if (err != null) + throw err; + } + + /* + * Returns a list of all linked resources at or below this resource, or null if there + * are no links. + */ + private List findLinks() { + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + HashMap linkMap = description.getLinks(); + if (linkMap == null) + return null; + List links = null; + IPath myPath = getProjectRelativePath(); + for (Iterator it = linkMap.values().iterator(); it.hasNext();) { + LinkDescription link = it.next(); + IPath linkPath = link.getProjectRelativePath(); + if (myPath.isPrefixOf(linkPath)) { + if (links == null) + links = new ArrayList(); + links.add(workspace.newResource(project.getFullPath().append(linkPath), link.getType())); + } + } + return links; + } + + /* + * Returns a list of all filtered resources at or below this resource, or null if there + * are no links. + */ + private List findFilters() { + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + List filters = null; + if (description != null) { + HashMap> filterMap = description.getFilters(); + if (filterMap != null) { + IPath myPath = getProjectRelativePath(); + for (Iterator it = filterMap.keySet().iterator(); it.hasNext();) { + IPath filterPath = it.next(); + if (myPath.isPrefixOf(filterPath)) { + if (filters == null) + filters = new ArrayList(); + filters.add(workspace.newResource(project.getFullPath().append(filterPath), IResource.FOLDER)); + } + } + } + } + return filters; + } + + @Override + public boolean equals(Object target) { + if (this == target) + return true; + if (!(target instanceof Resource)) + return false; + Resource resource = (Resource) target; + return getType() == resource.getType() && path.equals(resource.path) && workspace.equals(resource.workspace); + } + + @Override + public boolean exists() { + ResourceInfo info = getResourceInfo(false, false); + return exists(getFlags(info), true); + } + + public boolean exists(int flags, boolean checkType) { + return flags != NULL_FLAG && !(checkType && ResourceInfo.getType(flags) != getType()); + } + + /** + * Helper method for case insensitive file systems. Returns + * an existing resource whose path differs only in case from + * the given path, or null if no such resource exists. + */ + public IResource findExistingResourceVariant(IPath target) { + if (!workspace.tree.includesIgnoreCase(target)) + return null; + //ignore phantoms + ResourceInfo info = (ResourceInfo) workspace.tree.getElementDataIgnoreCase(target); + if (info != null && info.isSet(M_PHANTOM)) + return null; + //resort to slow lookup to find exact case variant + IPath result = Path.ROOT; + int segmentCount = target.segmentCount(); + for (int i = 0; i < segmentCount; i++) { + String[] childNames = workspace.tree.getNamesOfChildren(result); + String name = findVariant(target.segment(i), childNames); + if (name == null) + return null; + result = result.append(name); + } + return workspace.getRoot().findMember(result); + } + + @Override + public IMarker findMarker(long id) { + return workspace.getMarkerManager().findMarker(this, id); + } + + @Override + public IMarker[] findMarkers(String type, boolean includeSubtypes, int depth) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + // It might happen that from this point the resource is not accessible anymore. + // But markers have the #exists method that callers can use to check if it is + // still valid. + return workspace.getMarkerManager().findMarkers(this, type, includeSubtypes, depth); + } + + @Override + public int findMaxProblemSeverity(String type, boolean includeSubtypes, int depth) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + // It might happen that from this point the resource is not accessible anymore. + // But markers have the #exists method that callers can use to check if it is + // still valid. + return workspace.getMarkerManager().findMaxProblemSeverity(this, type, includeSubtypes, depth); + } + + /** + * Searches for a variant of the given target in the list, + * that differs only in case. Returns the variant from + * the list if one is found, otherwise returns null. + */ + private String findVariant(String target, String[] list) { + for (int i = 0; i < list.length; i++) { + if (target.toUpperCase().equals(list[i].toUpperCase())) + return list[i]; + } + return null; + } + + protected void fixupAfterMoveSource() throws CoreException { + ResourceInfo info = getResourceInfo(true, true); + //if a linked resource is moved, we need to remove the location info from the .project + if (isLinked() || isVirtual()) { + Project project = (Project) getProject(); + if (project.internalGetDescription().setLinkLocation(getProjectRelativePath(), null)) + project.writeDescription(IResource.NONE); + } + + List filters = findFilters(); + if ((filters != null) && (filters.size() > 0)) { + // delete resource filters + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + for (Iterator it = filters.iterator(); it.hasNext();) + description.setFilters(it.next().getProjectRelativePath(), null); + project.writeDescription(IResource.NONE); + } + + // check if we deleted a preferences file + ProjectPreferences.deleted(this); + + if (!synchronizing(info)) { + workspace.deleteResource(this); + return; + } + info.clearSessionProperties(); + info.clear(M_LOCAL_EXISTS); + info.setLocalSyncInfo(I_NULL_SYNC_INFO); + info.set(M_PHANTOM); + info.clearModificationStamp(); + info.setMarkers(null); + } + + @Override + public String getFileExtension() { + String name = getName(); + int index = name.lastIndexOf('.'); + if (index == -1) + return null; + if (index == (name.length() - 1)) + return ""; //$NON-NLS-1$ + return name.substring(index + 1); + } + + public int getFlags(ResourceInfo info) { + return (info == null) ? NULL_FLAG : info.getFlags(); + } + + @Override + public IPath getFullPath() { + return path; + } + + public FileSystemResourceManager getLocalManager() { + return workspace.getFileSystemManager(); + } + + @Override + public long getLocalTimeStamp() { + ResourceInfo info = getResourceInfo(false, false); + return (info == null || isVirtual()) ? IResource.NULL_STAMP : info.getLocalSyncInfo(); + } + + @Override + public IPath getLocation() { + IProject project = getProject(); + if (project != null && !project.exists()) + return null; + return getLocalManager().locationFor(this, false); + } + + @Override + public URI getLocationURI() { + IProject project = getProject(); + if (project != null && !project.exists()) + return null; + return getLocalManager().locationURIFor(this, false); + } + + @Override + public IMarker getMarker(long id) { + return new Marker(this, id); + } + + protected MarkerManager getMarkerManager() { + return workspace.getMarkerManager(); + } + + @Override + public long getModificationStamp() { + ResourceInfo info = getResourceInfo(false, false); + return info == null ? IResource.NULL_STAMP : info.getModificationStamp(); + } + + @Override + public String getName() { + return path.lastSegment(); + } + + @Override + public IContainer getParent() { + int segments = path.segmentCount(); + //zero and one segments handled by subclasses + if (segments < 2) + Assert.isLegal(false, path.toString()); + if (segments == 2) + return workspace.getRoot().getProject(path.segment(0)); + return (IFolder) workspace.newResource(path.removeLastSegments(1), IResource.FOLDER); + } + + @Override + public String getPersistentProperty(QualifiedName key) throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + return getPropertyManager().getProperty(this, key); + } + + @Override + public Map getPersistentProperties() throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + return getPropertyManager().getProperties(this); + } + + @Override + public IProject getProject() { + return workspace.getRoot().getProject(path.segment(0)); + } + + @Override + public IPath getProjectRelativePath() { + return getFullPath().removeFirstSegments(ICoreConstants.PROJECT_SEGMENT_LENGTH); + } + + public IPropertyManager getPropertyManager() { + return workspace.getPropertyManager(); + } + + @Override + public IPath getRawLocation() { + if (isLinked()) + return FileUtil.toPath(((Project) getProject()).internalGetDescription().getLinkLocationURI(getProjectRelativePath())); + return getLocation(); + } + + @Override + public URI getRawLocationURI() { + if (isLinked()) + return ((Project) getProject()).internalGetDescription().getLinkLocationURI(getProjectRelativePath()); + return getLocationURI(); + } + + @Override + public ResourceAttributes getResourceAttributes() { + if (!isAccessible() || isVirtual()) + return null; + return getLocalManager().attributes(this); + } + + /** + * Returns the resource info. Returns null if the resource doesn't exist. + * If the phantom flag is true, phantom resources are considered. + * If the mutable flag is true, a mutable info is returned. + */ + public ResourceInfo getResourceInfo(boolean phantom, boolean mutable) { + return workspace.getResourceInfo(getFullPath(), phantom, mutable); + } + + @Override + public Object getSessionProperty(QualifiedName key) throws CoreException { + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + return info.getSessionProperty(key); + } + + @Override + public Map getSessionProperties() throws CoreException { + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + return info.getSessionProperties(); + } + + public IFileStore getStore() { + return getLocalManager().getStore(this); + } + + @Override + public abstract int getType(); + + public String getTypeString() { + switch (getType()) { + case FILE : + return "L"; //$NON-NLS-1$ + case FOLDER : + return "F"; //$NON-NLS-1$ + case PROJECT : + return "P"; //$NON-NLS-1$ + case ROOT : + return "R"; //$NON-NLS-1$ + } + return ""; //$NON-NLS-1$ + } + + @Override + public IWorkspace getWorkspace() { + return workspace; + } + + @Override + public int hashCode() { + // the container may be null if the identified resource + // does not exist so don't bother with it in the hash + return getFullPath().hashCode(); + } + + /** + * Sets the M_LOCAL_EXISTS flag. Is internal so we don't have + * to begin an operation. + */ + protected void internalSetLocal(boolean flag, int depth) throws CoreException { + ResourceInfo info = getResourceInfo(true, true); + //only make the change if it's not already in desired state + if (info.isSet(M_LOCAL_EXISTS) != flag) { + if (flag && !isPhantom(getFlags(info))) { + info.set(M_LOCAL_EXISTS); + workspace.updateModificationStamp(info); + } else { + info.clear(M_LOCAL_EXISTS); + info.clearModificationStamp(); + } + } + if (getType() == IResource.FILE || depth == IResource.DEPTH_ZERO) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.DEPTH_ZERO; + IResource[] children = ((IContainer) this).members(); + for (int i = 0; i < children.length; i++) + ((Resource) children[i]).internalSetLocal(flag, depth); + } + + @Override + public boolean isAccessible() { + return exists(); + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + if (this == rule) + return true; + //must not schedule at same time as notification + if (rule.getClass().equals(WorkManager.NotifyRule.class)) + return true; + if (rule instanceof MultiRule) { + MultiRule multi = (MultiRule) rule; + ISchedulingRule[] children = multi.getChildren(); + for (int i = 0; i < children.length; i++) + if (isConflicting(children[i])) + return true; + return false; + } + if (!(rule instanceof IResource)) + return false; + IResource resource = (IResource) rule; + if (!workspace.equals(resource.getWorkspace())) + return false; + IPath otherPath = resource.getFullPath(); + return path.isPrefixOf(otherPath) || otherPath.isPrefixOf(path); + } + + @Override + public boolean isDerived() { + return isDerived(IResource.NONE); + } + + @Override + public boolean isDerived(int options) { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_DERIVED)) + return true; + // check ancestors if the appropriate option is set + if ((options & CHECK_ANCESTORS) != 0) + return getParent().isDerived(options); + return false; + } + + @Override + public boolean isHidden() { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_HIDDEN); + } + + @Override + public boolean isHidden(int options) { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_HIDDEN)) + return true; + // check ancestors if the appropriate option is set + if ((options & CHECK_ANCESTORS) != 0) + return getParent().isHidden(options); + return false; + } + + @Override + public boolean isLinked() { + return isLinked(NONE); + } + + @Override + public boolean isLinked(int options) { + if ((options & CHECK_ANCESTORS) != 0) { + IProject project = getProject(); + if (project == null) + return false; + ProjectDescription desc = ((Project) project).internalGetDescription(); + if (desc == null) + return false; + HashMap links = desc.getLinks(); + if (links == null) + return false; + IPath myPath = getProjectRelativePath(); + for (Iterator it = links.values().iterator(); it.hasNext();) { + if (it.next().getProjectRelativePath().isPrefixOf(myPath)) + return true; + } + return false; + } + //the no ancestor checking case + ResourceInfo info = getResourceInfo(false, false); + return info != null && info.isSet(M_LINK); + } + + @Override + public boolean isVirtual() { + ResourceInfo info = getResourceInfo(false, false); + return info != null && info.isSet(M_VIRTUAL); + } + + /* + * @return whether the current resource has a parent that is virtual. + */ + public boolean isUnderVirtual() { + IContainer parent = getParent(); + while (parent != null) { + if (parent.isVirtual()) + return true; + parent = parent.getParent(); + } + return false; + } + + @Override + @Deprecated + public boolean isLocal(int depth) { + ResourceInfo info = getResourceInfo(false, false); + return isLocal(getFlags(info), depth); + } + + /** + * Note the depth parameter is intentionally ignored because + * this method is over-ridden by Container.isLocal(). + * @deprecated + */ + @Deprecated + public boolean isLocal(int flags, int depth) { + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_LOCAL_EXISTS); + } + + /** + * Returns whether a resource should be included in a traversal + * based on the provided member flags. + * + * @param flags The resource info flags + * @param memberFlags The member flag mask + * @return Whether the resource is included + */ + protected boolean isMember(int flags, int memberFlags) { + int excludeMask = 0; + if ((memberFlags & IContainer.INCLUDE_PHANTOMS) == 0) + excludeMask |= M_PHANTOM; + if ((memberFlags & IContainer.INCLUDE_HIDDEN) == 0) + excludeMask |= M_HIDDEN; + if ((memberFlags & IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS) == 0) + excludeMask |= M_TEAM_PRIVATE_MEMBER; + if ((memberFlags & IContainer.EXCLUDE_DERIVED) != 0) + excludeMask |= M_DERIVED; + //the resource is a matching member if it matches none of the exclude flags + return flags != NULL_FLAG && (flags & excludeMask) == 0; + } + + @Override + public boolean isPhantom() { + ResourceInfo info = getResourceInfo(true, false); + return isPhantom(getFlags(info)); + } + + public boolean isPhantom(int flags) { + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_PHANTOM); + } + + @Deprecated + @Override + public boolean isReadOnly() { + final ResourceAttributes attributes = getResourceAttributes(); + return attributes == null ? false : attributes.isReadOnly(); + } + + @Override + public boolean isSynchronized(int depth) { + return getLocalManager().isSynchronized(this, depth); + } + + @Override + public boolean isTeamPrivateMember() { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_TEAM_PRIVATE_MEMBER); + } + + @Override + public boolean isTeamPrivateMember(int options) { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_TEAM_PRIVATE_MEMBER)) + return true; + // check ancestors if the appropriate option is set + if ((options & CHECK_ANCESTORS) != 0) + return getParent().isTeamPrivateMember(options); + return false; + } + + /** + * Returns true if this resource is a linked resource, or a child of a linked + * resource, and false otherwise. + */ + public boolean isUnderLink() { + int depth = path.segmentCount(); + if (depth < 2) + return false; + if (depth == 2) + return isLinked(); + //check if parent at depth two is a link + IPath linkParent = path.removeLastSegments(depth - 2); + return workspace.getResourceInfo(linkParent, false, false).isSet(ICoreConstants.M_LINK); + } + + protected IPath makePathAbsolute(IPath target) { + if (target.isAbsolute()) + return target; + return getParent().getFullPath().append(target); + } + + /* (non-Javadoc) + * @see IFile#move(IPath, boolean, boolean, IProgressMonitor) + * @see IFolder#move(IPath, boolean, boolean, IProgressMonitor) + */ + public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; + move(destination, updateFlags, monitor); + } + + @Override + public void move(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + move(destination, force ? IResource.FORCE : IResource.NONE, monitor); + } + + @Override + public void move(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_moving, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + Policy.checkCanceled(monitor); + destination = makePathAbsolute(destination); + checkValidPath(destination, getType(), false); + Resource destResource = workspace.newResource(destination, getType()); + final ISchedulingRule rule = workspace.getRuleFactory().moveRule(this, destResource); + try { + workspace.prepareOperation(rule, monitor); + // The following assert method throws CoreExceptions as stated in the IResource.move API + // and assert for programming errors. See checkMoveRequirements for more information. + assertMoveRequirements(destination, getType(), updateFlags); + workspace.beginOperation(true); + broadcastPreMoveEvent(destResource, updateFlags); + IFileStore originalStore = getStore(); + message = Messages.resources_moveProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, null); + WorkManager workManager = workspace.getWorkManager(); + ResourceTree tree = new ResourceTree(workspace.getFileSystemManager(), workManager.getLock(), status, updateFlags); + boolean success = false; + int depth = 0; + try { + depth = workManager.beginUnprotected(); + success = unprotectedMove(tree, destResource, updateFlags, monitor); + } finally { + workManager.endUnprotected(depth); + } + // Invalidate the tree for further use by clients. + tree.makeInvalid(); + //update any aliases of this resource and the destination + if (success) { + workspace.getAliasManager().updateAliases(this, originalStore, IResource.DEPTH_INFINITE, monitor); + workspace.getAliasManager().updateAliases(destResource, destResource.getStore(), IResource.DEPTH_INFINITE, monitor); + } + if (!tree.getStatus().isOK()) + throw new ResourceException(tree.getStatus()); + // if this is a project, make sure the move operation is remembered + if (getType() == PROJECT) + workspace.getSaveManager().requestSnapshot(); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void move(IProjectDescription description, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; + move(description, updateFlags, monitor); + } + + @Override + public void move(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(description); + if (getType() != IResource.PROJECT) { + String message = NLS.bind(Messages.resources_moveNotProject, getFullPath(), description.getName()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + ((Project) this).move(description, updateFlags, monitor); + } + + @Override + public void refreshLocal(int depth, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + boolean isRoot = getType() == ROOT; + String message = isRoot ? Messages.resources_refreshingRoot : NLS.bind(Messages.resources_refreshing, getFullPath()); + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + monitor.subTask(message); + boolean build = false; + final ISchedulingRule rule = workspace.getRuleFactory().refreshRule(this); + try { + workspace.prepareOperation(rule, monitor); + if (!isRoot && !getProject().isAccessible()) + return; + if (!exists() && isFiltered()) + return; + workspace.beginOperation(true); + if (getType() == IResource.PROJECT || getType() == IResource.ROOT) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_REFRESH, this)); + build = getLocalManager().refresh(this, depth, true, Policy.subMonitorFor(monitor, Policy.opWork)); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, build, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public String requestName() { + return getName(); + } + + @Override + public IPath requestPath() { + return getFullPath(); + } + + @Override + public void revertModificationStamp(long value) throws CoreException { + if (value < 0) + throw new IllegalArgumentException("Illegal value: " + value); //$NON-NLS-1$ + // fetch the info but don't bother making it mutable even though we are going + // to modify it. It really doesn't matter as the change we are doing does not show up in deltas. + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + info.setModificationStamp(value); + } + + @Deprecated + @Override + public void setDerived(boolean isDerived) throws CoreException { + // fetch the info but don't bother making it mutable even though we are going + // to modify it. We don't know whether or not the tree is open and it really doesn't + // matter as the change we are doing does not show up in deltas. + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + // ignore attempts to set derived flag on anything except files and folders + if (info.getType() == FILE || info.getType() == FOLDER) { + if (isDerived) { + info.set(ICoreConstants.M_DERIVED); + } else { + info.clear(ICoreConstants.M_DERIVED); + } + } + } + + @Override + public void setDerived(boolean isDerived, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_settingDerivedFlag, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + final ISchedulingRule rule = workspace.getRuleFactory().derivedRule(this); + try { + workspace.prepareOperation(rule, monitor); + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + // ignore attempts to set derived flag on anything except files and folders + if (info.getType() != FILE && info.getType() != FOLDER) + return; + workspace.beginOperation(true); + info = getResourceInfo(false, true); + if (isDerived) + info.set(ICoreConstants.M_DERIVED); + else { + info.clear(ICoreConstants.M_DERIVED); + } + 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 setHidden(boolean isHidden) throws CoreException { + // fetch the info but don't bother making it mutable even though we are going + // to modify it. We don't know whether or not the tree is open and it really doesn't + // matter as the change we are doing does not show up in deltas. + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + if (isHidden) { + info.set(ICoreConstants.M_HIDDEN); + } else { + info.clear(ICoreConstants.M_HIDDEN); + } + } + + @Deprecated + @Override + public void setLocal(boolean flag, int depth, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = Messages.resources_setLocal; + monitor.beginTask(message, Policy.totalWork); + try { + workspace.prepareOperation(null, monitor); + workspace.beginOperation(true); + internalSetLocal(flag, depth); + monitor.worked(Policy.opWork); + } finally { + workspace.endOperation(null, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public long setLocalTimeStamp(long value) throws CoreException { + if (value < 0) + throw new IllegalArgumentException("Illegal value: " + value); //$NON-NLS-1$ + // fetch the info but don't bother making it mutable even though we are going + // to modify it. It really doesn't matter as the change we are doing does not show up in deltas. + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + return getLocalManager().setLocalTimeStamp(this, info, value); + } + + @Override + public void setPersistentProperty(QualifiedName key, String value) throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + getPropertyManager().setProperty(this, key, value); + } + + @Deprecated + @Override + public void setReadOnly(boolean readonly) { + ResourceAttributes attributes = getResourceAttributes(); + if (attributes == null) + return; + attributes.setReadOnly(readonly); + try { + setResourceAttributes(attributes); + } catch (CoreException e) { + //failure is not an option + } + } + + @Override + public void setResourceAttributes(ResourceAttributes attributes) throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + getLocalManager().setResourceAttributes(this, attributes); + } + + @Override + public void setSessionProperty(QualifiedName key, Object value) throws CoreException { + // fetch the info but don't bother making it mutable even though we are going + // to modify it. We don't know whether or not the tree is open and it really doesn't + // matter as the change we are doing does not show up in deltas. + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + info.setSessionProperty(key, value); + } + + @Override + public void setTeamPrivateMember(boolean isTeamPrivate) throws CoreException { + // fetch the info but don't bother making it mutable even though we are going + // to modify it. We don't know whether or not the tree is open and it really doesn't + // matter as the change we are doing does not show up in deltas. + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + // ignore attempts to set team private member flag on anything except files and folders + if (info.getType() == FILE || info.getType() == FOLDER) { + if (isTeamPrivate) { + info.set(ICoreConstants.M_TEAM_PRIVATE_MEMBER); + } else { + info.clear(ICoreConstants.M_TEAM_PRIVATE_MEMBER); + } + } + } + + /** + * Returns true if this resource has the potential to be + * (or have been) synchronized. + */ + public boolean synchronizing(ResourceInfo info) { + return info != null && info.getSyncInfo(false) != null; + } + + @Override + public String toString() { + return getTypeString() + getFullPath().toString(); + } + + @Override + public void touch(IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_touch, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, monitor); + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + + workspace.beginOperation(true); + // fake a change by incrementing the content ID + info = getResourceInfo(false, true); + info.incrementContentId(); + // forget content-related caching flags + info.clear(M_CONTENT_CACHE); + workspace.updateModificationStamp(info); + 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(); + } + } + + /** + * Calls the move/delete hook to perform the deletion. Since this method calls + * client code, it is run "unprotected", so the workspace lock is not held. + */ + private void unprotectedDelete(ResourceTree tree, int updateFlags, IProgressMonitor monitor) { + IMoveDeleteHook hook = workspace.getMoveDeleteHook(); + switch (getType()) { + case IResource.FILE : + if (!hook.deleteFile(tree, (IFile) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000 / 2))) + tree.standardDeleteFile((IFile) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000)); + break; + case IResource.FOLDER : + if (!hook.deleteFolder(tree, (IFolder) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000 / 2))) + tree.standardDeleteFolder((IFolder) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000)); + break; + case IResource.PROJECT : + if (!hook.deleteProject(tree, (IProject) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000 / 2))) + tree.standardDeleteProject((IProject) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000)); + break; + case IResource.ROOT : + // when the root is deleted, all its children including hidden projects + // have to be deleted + IProject[] projects = ((IWorkspaceRoot) this).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) { + if (!hook.deleteProject(tree, projects[i], updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000 / projects.length / 2))) + tree.standardDeleteProject(projects[i], updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000 / projects.length)); + } + } + } + + /** + * Calls the move/delete hook to perform the move. Since this method calls + * client code, it is run "unprotected", so the workspace lock is not held. + * Returns true if resources were actually moved, and false otherwise. + */ + private boolean unprotectedMove(ResourceTree tree, final IResource destination, int updateFlags, IProgressMonitor monitor) throws CoreException, ResourceException { + IMoveDeleteHook hook = workspace.getMoveDeleteHook(); + switch (getType()) { + case IResource.FILE : + if (!hook.moveFile(tree, (IFile) this, (IFile) destination, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2))) + tree.standardMoveFile((IFile) this, (IFile) destination, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork)); + break; + case IResource.FOLDER : + if (!hook.moveFolder(tree, (IFolder) this, (IFolder) destination, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2))) + tree.standardMoveFolder((IFolder) this, (IFolder) destination, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork)); + break; + case IResource.PROJECT : + IProject project = (IProject) this; + // if there is no change in name, there is nothing to do so return. + if (getName().equals(destination.getName())) + return false; + IProjectDescription description = project.getDescription(); + description.setName(destination.getName()); + if (!hook.moveProject(tree, project, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2))) + tree.standardMoveProject(project, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork)); + break; + case IResource.ROOT : + String msg = Messages.resources_moveRoot; + throw new ResourceException(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), msg)); + } + return true; + } + + private void broadcastPreDeleteEvent() throws CoreException { + switch (getType()) { + case IResource.PROJECT : + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_DELETE, this)); + break; + case IResource.ROOT : + // all root children including hidden projects will be deleted so notify + IResource[] projects = ((Container) this).getChildren(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_DELETE, projects[i])); + } + } + + private void broadcastPreMoveEvent(final IResource destination, int updateFlags) throws CoreException { + switch (getType()) { + case IResource.FILE : + if (isLinked()) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_MOVE, this, destination, updateFlags)); + break; + case IResource.FOLDER : + if (isLinked()) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_MOVE, this, destination, updateFlags)); + if (isVirtual()) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_GROUP_MOVE, this, destination, updateFlags)); + break; + case IResource.PROJECT : + if (!getName().equals(destination.getName())) { + // if there is a change in name, we are deleting the source project so notify. + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_MOVE, this, destination, updateFlags)); + } + break; + } + } + + @Override + public IPathVariableManager getPathVariableManager() { + if (getProject() == null) + return workspace.getPathVariableManager(); + return new ProjectPathVariableManager(this); + } + + /* (non-Javadoc) + * Calculates whether the current resource is filtered out from the resource tree + * by resource filters. This can happen because resource filters apply to the resource, + * or because resource filters apply to one of its parent. For example, if "/foo/bar" + * is filtered out, then calling isFilteredFromParent() on "/foo/bar/sub/file.txt" will + * return true as well, even though there's no resource filters that apply to "file.txt" per se. + * + * @return true is the resource is filtered out from the resource tree + * @see IResource#isFiltered() + */ + public boolean isFiltered() { + try { + return isFilteredWithException(false); + } catch (CoreException e) { + // nothing + } + return false; + } + + public boolean isFilteredWithException(boolean throwExeception) throws CoreException { + if (isLinked() || isVirtual()) + return false; + + Project project = (Project) getProject(); + if (project == null) + return false; + final ProjectDescription description = project.internalGetDescription(); + if (description == null) + return false; + if (description.getFilters() == null) + return false; + + Resource currentResource = this; + while (currentResource != null && currentResource.getParent() != null) { + Resource parent = (Resource) currentResource.getParent(); + IFileStore store = currentResource.getStore(); + if (store != null) { + FileInfo fileInfo = new FileInfo(store.getName()); + fileInfo.setDirectory(currentResource.getType() == IResource.FOLDER); + if (fileInfo != null) { + IFileInfo[] filtered = parent.filterChildren(project, description, new IFileInfo[] {fileInfo}, throwExeception); + if (filtered.length == 0) + return true; + } + } + currentResource = parent; + } + return false; + } + + public IFileInfo[] filterChildren(IFileInfo[] list, boolean throwException) throws CoreException { + Project project = (Project) getProject(); + if (project == null) + return list; + final ProjectDescription description = project.internalGetDescription(); + if (description == null) + return list; + return filterChildren(project, description, list, throwException); + } + + private IFileInfo[] filterChildren(Project project, ProjectDescription description, IFileInfo[] list, boolean throwException) throws CoreException { + IPath relativePath = getProjectRelativePath(); + LinkedList currentIncludeFilters = new LinkedList(); + LinkedList currentExcludeFilters = new LinkedList(); + LinkedList filters = null; + + boolean firstSegment = true; + do { + if (!firstSegment) + relativePath = relativePath.removeLastSegments(1); + filters = description.getFilter(relativePath); + if (filters != null) { + for (Iterator it = filters.iterator(); it.hasNext();) { + FilterDescription desc = it.next(); + if (firstSegment || desc.isInheritable()) { + Filter filter = new Filter(project, desc); + if (filter.isIncludeOnly()) { + if (filter.isFirst()) + currentIncludeFilters.addFirst(filter); + else + currentIncludeFilters.addLast(filter); + } else { + if (filter.isFirst()) + currentExcludeFilters.addFirst(filter); + else + currentExcludeFilters.addLast(filter); + } + } + } + } + firstSegment = false; + } while (relativePath.segmentCount() > 0); + + if ((currentIncludeFilters.size() > 0) || (currentExcludeFilters.size() > 0)) { + try { + list = Filter.filter(project, currentIncludeFilters, currentExcludeFilters, (IContainer) this, list); + } catch (CoreException e) { + if (throwException) + throw e; + } + } + return list; + } + + /* + * (non-Javadoc) + * + * @see IResource#setLinkLocation(IPath) + */ + public void setLinkLocation(URI location, int updateFlags, IProgressMonitor monitor) throws CoreException { + if (!isLinked()) { + String message = NLS.bind(Messages.links_resourceIsNotALink, getFullPath()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + + final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); + try { + String message = NLS.bind(Messages.links_setLocation, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + + workspace.prepareOperation(rule, monitor); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_CHANGE, this)); + workspace.beginOperation(true); + + ResourceInfo info = workspace.getResourceInfo(getFullPath(), true, false); + getLocalManager().setLocation(this, info, location); + + LinkDescription linkDescription; + linkDescription = new LinkDescription(this, location); + Project project = (Project) getProject(); + project.internalGetDescription().setLinkLocation(getProjectRelativePath(), linkDescription); + project.writeDescription(updateFlags); + + // 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)); + } + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } + + /* + * (non-Javadoc) + * + * @see IResource#setLinkLocation(URI) + */ + public void setLinkLocation(IPath location, int updateFlags, IProgressMonitor monitor) throws CoreException { + if (location.isAbsolute()) { + setLinkLocation(URIUtil.toURI(location.toPortableString()), updateFlags, monitor); + } else { + try { + setLinkLocation(new URI(null, null, location.toPortableString(), null), updateFlags, monitor); + } catch (URISyntaxException e) { + setLinkLocation(URIUtil.toURI(location.toPortableString()), updateFlags, monitor); + } + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java new file mode 100644 index 0000000000..288ac8c454 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * 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 java.io.PrintStream; +import java.io.PrintWriter; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; + +/** + * A checked exception representing a failure. + *

+ * Resource exceptions contain a status object describing the cause of the + * exception, and optionally the path of the resource where the failure + * occurred. + *

+ * + * @see IStatus + */ +public class ResourceException extends CoreException { + /** + * All serializable objects should have a stable serialVersionUID + */ + private static final long serialVersionUID = 1L; + + public ResourceException(int code, IPath path, String message, Throwable exception) { + super(new ResourceStatus(code, path, message, exception)); + } + + /** + * Constructs a new exception with the given status object. + * + * @param status the status object to be associated with this exception + * @see IStatus + */ + public ResourceException(IStatus status) { + super(status); + } + + /** + * Prints a stack trace out for the exception, and + * any nested exception that it may have embedded in + * its Status object. + */ + @Override + public void printStackTrace() { + printStackTrace(System.err); + } + + /** + * Prints a stack trace out for the exception, and + * any nested exception that it may have embedded in + * its Status object. + */ + @Override + public void printStackTrace(PrintStream output) { + synchronized (output) { + IStatus status = getStatus(); + if (status.getException() != null) { + String path = "()"; //$NON-NLS-1$ + if (status instanceof IResourceStatus) + path = "(" + ((IResourceStatus) status).getPath() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + output.print(getClass().getName() + path + "[" + status.getCode() + "]: "); //$NON-NLS-1$ //$NON-NLS-2$ + status.getException().printStackTrace(output); + } else + super.printStackTrace(output); + } + } + + /** + * Prints a stack trace out for the exception, and + * any nested exception that it may have embedded in + * its Status object. + */ + @Override + public void printStackTrace(PrintWriter output) { + synchronized (output) { + IStatus status = getStatus(); + if (status.getException() != null) { + String path = "()"; //$NON-NLS-1$ + if (status instanceof IResourceStatus) + path = "(" + ((IResourceStatus) status).getPath() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + output.print(getClass().getName() + path + "[" + status.getCode() + "]: "); //$NON-NLS-1$ //$NON-NLS-2$ + status.getException().printStackTrace(output); + } else + super.printStackTrace(output); + } + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java new file mode 100644 index 0000000000..0b1ab90a8b --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java @@ -0,0 +1,476 @@ +/******************************************************************************* + * 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 + * Oakland Software Incorporated - added getSessionProperties and getPersistentProperties + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.Map; +import org.eclipse.core.internal.localstore.FileStoreRoot; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.internal.watson.IElementTreeData; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.QualifiedName; + +/** + * A data structure containing the in-memory state of a resource in the workspace. + */ +public class ResourceInfo implements IElementTreeData, ICoreConstants, IStringPoolParticipant { + protected static final int LOWER = 0xFFFF; + protected static final int UPPER = 0xFFFF0000; + + /** + * This field stores the resource modification stamp in the lower two bytes, + * and the character set generation count in the higher two bytes. + */ + protected volatile int charsetAndContentId = 0; + + /** + * The file system root that this resource is stored in + */ + protected FileStoreRoot fileStoreRoot; + + /** Set of flags which reflect various states of the info (used, derived, ...). */ + protected int flags = 0; + + /** Local sync info */ + // thread safety: (Concurrency004) + protected volatile long localInfo = I_NULL_SYNC_INFO; + + /** + * This field stores the sync info generation in the lower two bytes, and + * the marker generation count in the upper two bytes. + */ + protected volatile int markerAndSyncStamp; + + /** The collection of markers for this resource. */ + protected MarkerSet markers = null; + + /** Modification stamp */ + protected long modStamp = 0; + + /** Unique node identifier */ + // thread safety: (Concurrency004) + protected volatile long nodeId = 0; + + /** + * The properties which are maintained for the lifecycle of the workspace. + *

+ * This field is declared as the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + protected ObjectMap sessionProperties = null; + + /** + * The table of sync information. + *

+ * This field is declared as the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + protected ObjectMap syncInfo = null; + + /** + * Returns the integer value stored in the indicated part of this info's flags. + */ + protected static int getBits(int flags, int mask, int start) { + return (flags & mask) >> start; + } + + /** + * Returns the type setting for this info. Valid values are + * FILE, FOLDER, PROJECT, + */ + public static int getType(int flags) { + return getBits(flags, M_TYPE, M_TYPE_START); + } + + /** + * Returns true if all of the bits indicated by the mask are set. + */ + public static boolean isSet(int flags, int mask) { + return (flags & mask) == mask; + } + + /** + * Clears all of the bits indicated by the mask. + */ + public void clear(int mask) { + flags &= ~mask; + } + + public void clearModificationStamp() { + modStamp = IResource.NULL_STAMP; + } + + public synchronized void clearSessionProperties() { + sessionProperties = null; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; // never gets here. + } + } + + public int getCharsetGenerationCount() { + return charsetAndContentId >> 16; + } + + public int getContentId() { + return charsetAndContentId & LOWER; + } + + public FileStoreRoot getFileStoreRoot() { + return fileStoreRoot; + } + + /** + * Returns the set of flags for this info. + */ + public int getFlags() { + return flags; + } + + /** + * Gets the local-relative sync information. + */ + public long getLocalSyncInfo() { + return localInfo; + } + + /** + * Returns the marker generation count. + * The count is incremented whenever markers on the resource change. + */ + public int getMarkerGenerationCount() { + return markerAndSyncStamp >> 16; + } + + /** + * Returns a copy of the collection of makers on this resource. + * null is returned if there are none. + */ + public MarkerSet getMarkers() { + return getMarkers(true); + } + + /** + * Returns the collection of makers on this resource. + * null is returned if there are none. + */ + public MarkerSet getMarkers(boolean makeCopy) { + if (markers == null) + return null; + return makeCopy ? (MarkerSet) markers.clone() : markers; + } + + public long getModificationStamp() { + return modStamp; + } + + public long getNodeId() { + return nodeId; + } + + /** + * Returns the property store associated with this info. The return value may be null. + */ + public Object getPropertyStore() { + return null; + } + + /** + * Returns a copy of the map of this resource session properties. + * An empty map is returned if there are none. + */ + @SuppressWarnings({"unchecked"}) + public Map getSessionProperties() { + // thread safety: (Concurrency001) + ObjectMap temp = sessionProperties; + if (temp == null) + temp = new ObjectMap(5); + else + temp = (ObjectMap) sessionProperties.clone(); + return temp; + } + + /** + * Returns the value of the identified session property + */ + public Object getSessionProperty(QualifiedName name) { + // thread safety: (Concurrency001) + Map temp = sessionProperties; + if (temp == null) + return null; + return temp.get(name); + } + + /** + * The parameter to this method is the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + @SuppressWarnings({"unchecked"}) + public synchronized ObjectMap getSyncInfo(boolean makeCopy) { + if (syncInfo == null) + return null; + return makeCopy ? (ObjectMap) syncInfo.clone() : syncInfo; + } + + public synchronized byte[] getSyncInfo(QualifiedName id, boolean makeCopy) { + // thread safety: (Concurrency001) + byte[] b; + if (syncInfo == null) + return null; + b = (byte[]) syncInfo.get(id); + return b == null ? null : (makeCopy ? (byte[]) b.clone() : b); + } + + /** + * Returns the sync information generation count. + * The count is incremented whenever sync info on the resource changes. + */ + public int getSyncInfoGenerationCount() { + return markerAndSyncStamp & LOWER; + } + + /** + * Returns the type setting for this info. Valid values are + * FILE, FOLDER, PROJECT, + */ + public int getType() { + return getType(flags); + } + + /** + * Increments the charset generation count. + * The count is incremented whenever the encoding on the resource changes. + */ + public void incrementCharsetGenerationCount() { + //increment high order bits + charsetAndContentId = ((charsetAndContentId + LOWER + 1) & UPPER) + (charsetAndContentId & LOWER); + } + + /** + * Mark this resource info as having changed content + */ + public void incrementContentId() { + //increment low order bits + charsetAndContentId = (charsetAndContentId & UPPER) + ((charsetAndContentId + 1) & LOWER); + } + + /** + * Increments the marker generation count. + * The count is incremented whenever markers on the resource change. + */ + public void incrementMarkerGenerationCount() { + //increment high order bits + markerAndSyncStamp = ((markerAndSyncStamp + LOWER + 1) & UPPER) + (markerAndSyncStamp & LOWER); + } + + /** + * Change the modification stamp to indicate that this resource has changed. + * The exact value of the stamp doesn't matter, as long as it can be used to + * distinguish two arbitrary resource generations. + */ + public void incrementModificationStamp() { + modStamp++; + } + + /** + * Increments the sync information generation count. + * The count is incremented whenever sync info on the resource changes. + */ + public void incrementSyncInfoGenerationCount() { + //increment low order bits + markerAndSyncStamp = (markerAndSyncStamp & UPPER) + ((markerAndSyncStamp + 1) & LOWER); + } + + /** + * Returns true if all of the bits indicated by the mask are set. + */ + public boolean isSet(int mask) { + return (flags & mask) == mask; + } + + public void readFrom(int newFlags, DataInput input) throws IOException { + // The flags for this info are read by the visitor (flattener). + // See Workspace.readElement(). This allows the reader to look ahead + // and see what type of info is being loaded. + this.flags = newFlags; + localInfo = input.readLong(); + nodeId = input.readLong(); + charsetAndContentId = input.readInt() & LOWER; + modStamp = input.readLong(); + } + + /** + * Sets all of the bits indicated by the mask. + */ + public void set(int mask) { + flags |= mask; + } + + /** + * Sets the value of the indicated bits to be the given value. + */ + protected void setBits(int mask, int start, int value) { + int baseMask = mask >> start; + int newValue = (value & baseMask) << start; + // thread safety: (guarantee atomic assignment) + int temp = flags; + temp &= ~mask; + temp |= newValue; + flags = temp; + } + + public void setFileStoreRoot(FileStoreRoot fileStoreRoot) { + this.fileStoreRoot = fileStoreRoot; + } + + /** + * Sets the flags for this info. + */ + protected void setFlags(int value) { + flags = value; + } + + /** + * Sets the local-relative sync information. + */ + public void setLocalSyncInfo(long info) { + localInfo = info; + } + + /** + * Sets the collection of makers for this resource. + * null is passed in if there are no markers. + */ + public void setMarkers(MarkerSet value) { + markers = value; + } + + /** + * Sets the resource modification stamp. + */ + public void setModificationStamp(long value) { + this.modStamp = value; + } + + /** + * + */ + public void setNodeId(long id) { + nodeId = id; + // Resource modification stamp starts from current nodeId + // so future generations are distinguishable (bug 160728) + if (modStamp == 0) + modStamp = nodeId; + } + + /** + * Sets the property store associated with this info. The value may be null. + */ + public void setPropertyStore(Object value) { + // needs to be implemented on subclasses + } + + /** + * Sets the identified session property to the given value. If + * the value is null, the property is removed. + */ + @SuppressWarnings({"unchecked"}) + public synchronized void setSessionProperty(QualifiedName name, Object value) { + // thread safety: (Concurrency001) + if (value == null) { + if (sessionProperties == null) + return; + ObjectMap temp = (ObjectMap) sessionProperties.clone(); + temp.remove(name); + if (temp.isEmpty()) + sessionProperties = null; + else + sessionProperties = temp; + } else { + ObjectMap temp = sessionProperties; + if (temp == null) + temp = new ObjectMap(5); + else + temp = (ObjectMap) sessionProperties.clone(); + temp.put(name, value); + sessionProperties = temp; + } + } + + /** + * The parameter to this method is the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + protected void setSyncInfo(ObjectMap syncInfo) { + this.syncInfo = syncInfo; + } + + public synchronized void setSyncInfo(QualifiedName id, byte[] value) { + if (value == null) { + //delete sync info + if (syncInfo == null) + return; + syncInfo.remove(id); + if (syncInfo.isEmpty()) + syncInfo = null; + } else { + //add sync info + if (syncInfo == null) + syncInfo = new ObjectMap(5); + syncInfo.put(id, value.clone()); + } + } + + /** + * Sets the type for this info to the given value. Valid values are + * FILE, FOLDER, PROJECT + */ + public void setType(int value) { + setBits(M_TYPE, M_TYPE_START, value); + } + + /* (non-Javadoc + * Method declared on IStringPoolParticipant + */ + @Override + public void shareStrings(StringPool set) { + ObjectMap map = syncInfo; + if (map != null) + map.shareStrings(set); + map = sessionProperties; + if (map != null) + map.shareStrings(set); + MarkerSet markerSet = markers; + if (markerSet != null) + markerSet.shareStrings(set); + } + + public void writeTo(DataOutput output) throws IOException { + // The flags for this info are written by the visitor (flattener). + // See SaveManager.writeElement(). This allows the reader to look ahead + // and see what type of info is being loaded. + output.writeLong(localInfo); + output.writeLong(nodeId); + output.writeInt(getContentId()); + output.writeLong(modStamp); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java new file mode 100644 index 0000000000..6c1ba9a456 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.internal.watson.IPathRequestor; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.QualifiedName; + +/** + * Implements a resource proxy given a path requestor and the resource + * info of the resource currently being visited. + */ +public class ResourceProxy implements IResourceProxy, ICoreConstants { + protected final Workspace workspace = (Workspace) ResourcesPlugin.getWorkspace(); + protected IPathRequestor requestor; + protected ResourceInfo info; + + //cached info + protected IPath fullPath; + protected IResource resource; + + /** + * @see org.eclipse.core.resources.IResourceProxy#getModificationStamp() + */ + @Override + public long getModificationStamp() { + return info.getModificationStamp(); + } + + @Override + public String getName() { + return requestor.requestName(); + } + + @Override + public Object getSessionProperty(QualifiedName key) { + return info.getSessionProperty(key); + } + + @Override + public int getType() { + return info.getType(); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isAccessible() + */ + @Override + public boolean isAccessible() { + int flags = info.getFlags(); + if (info.getType() == IResource.PROJECT) + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_OPEN); + return flags != NULL_FLAG; + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isDerived() + */ + @Override + public boolean isDerived() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_DERIVED); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isLinked() + */ + @Override + public boolean isLinked() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_LINK); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isPhantom() + */ + @Override + public boolean isPhantom() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_PHANTOM); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isTeamPrivateMember() + */ + @Override + public boolean isTeamPrivateMember() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_TEAM_PRIVATE_MEMBER); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isHidden() + */ + @Override + public boolean isHidden() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_HIDDEN); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#requestFullPath() + */ + @Override + public IPath requestFullPath() { + if (fullPath == null) + fullPath = requestor.requestPath(); + return fullPath; + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#requestResource() + */ + @Override + public IResource requestResource() { + if (resource == null) + resource = workspace.newResource(requestFullPath(), info.getType()); + return resource; + } + + protected void reset() { + fullPath = null; + resource = null; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java new file mode 100644 index 0000000000..ebc6501fae --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; + +/** + * + */ +public class ResourceStatus extends Status implements IResourceStatus { + IPath path; + + public ResourceStatus(int type, int code, IPath path, String message, Throwable exception) { + super(type, ResourcesPlugin.PI_RESOURCES, code, message, exception); + this.path = path; + } + + public ResourceStatus(int code, String message) { + this(getSeverity(code), code, null, message, null); + } + + public ResourceStatus(int code, IPath path, String message) { + this(getSeverity(code), code, path, message, null); + } + + public ResourceStatus(int code, IPath path, String message, Throwable exception) { + this(getSeverity(code), code, path, message, exception); + } + + /** + * @see IResourceStatus#getPath() + */ + @Override + public IPath getPath() { + return path; + } + + protected static int getSeverity(int code) { + return code == 0 ? 0 : 1 << (code % 100 / 33); + } + + // for debug only + private String getTypeName() { + switch (getSeverity()) { + case IStatus.OK : + return "OK"; //$NON-NLS-1$ + case IStatus.ERROR : + return "ERROR"; //$NON-NLS-1$ + case IStatus.INFO : + return "INFO"; //$NON-NLS-1$ + case IStatus.WARNING : + return "WARNING"; //$NON-NLS-1$ + default : + return String.valueOf(getSeverity()); + } + } + + // for debug only + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("[type: "); //$NON-NLS-1$ + sb.append(getTypeName()); + sb.append("], [path: "); //$NON-NLS-1$ + sb.append(getPath()); + sb.append("], [message: "); //$NON-NLS-1$ + sb.append(getMessage()); + sb.append("], [plugin: "); //$NON-NLS-1$ + sb.append(getPlugin()); + sb.append("], [exception: "); //$NON-NLS-1$ + sb.append(getException()); + sb.append("]\n"); //$NON-NLS-1$ + return sb.toString(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java new file mode 100644 index 0000000000..fef1644f99 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java @@ -0,0 +1,1165 @@ +/******************************************************************************* + * 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] Project Path Variable Support + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.localstore.FileSystemResourceManager; +import org.eclipse.core.internal.properties.IPropertyManager; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IResourceTree; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ILock; +import org.eclipse.osgi.util.NLS; + +/** + * @since 2.0 + * + * Implementation note: Since the move/delete hook involves running third + * party code, the workspace lock is not held. This means the workspace + * lock must be re-acquired whenever we need to manipulate the workspace + * in any way. All entry points from third party code back into the tree must + * be done in an acquire/release pair. + */ +class ResourceTree implements IResourceTree { + private boolean isValid = true; + private final FileSystemResourceManager localManager; + /** + * The lock to acquire when the workspace needs to be manipulated + */ + private ILock lock; + private MultiStatus multistatus; + private int updateFlags; + + /** + * Constructor for this class. + */ + public ResourceTree(FileSystemResourceManager localManager, ILock lock, MultiStatus status, int updateFlags) { + super(); + this.localManager = localManager; + this.lock = lock; + this.multistatus = status; + this.updateFlags = updateFlags; + } + + /** + * @see IResourceTree#addToLocalHistory(IFile) + */ + @Override + public void addToLocalHistory(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + if (!file.exists()) + return; + IFileStore store = localManager.getStore(file); + final IFileInfo fileInfo = store.fetchInfo(); + if (!fileInfo.exists()) + return; + localManager.getHistoryStore().addState(file.getFullPath(), store, fileInfo, false); + } finally { + lock.release(); + } + } + + private IFileStore computeDestinationStore(IProjectDescription destDescription) throws CoreException { + URI destLocation = destDescription.getLocationURI(); + // Use the default area if necessary for the destination. + if (destLocation == null) { + IPath rootLocation = ResourcesPlugin.getWorkspace().getRoot().getLocation(); + destLocation = rootLocation.append(destDescription.getName()).toFile().toURI(); + } + return EFS.getStore(destLocation); + } + + /** + * @see IResourceTree#computeTimestamp(IFile) + */ + @Override + public long computeTimestamp(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + if (!file.getProject().exists()) + return NULL_TIMESTAMP; + return internalComputeTimestamp(file); + } finally { + lock.release(); + } + } + + /** + * Copies the local history of source to destination. Note that if source + * is an IFolder, it is assumed that the same structure exists under destination + * and the local history of any IFile under source will be copied to the + * associated IFile under destination. + */ + private void copyLocalHistory(IResource source, IResource destination) { + localManager.getHistoryStore().copyHistory(source, destination, true); + } + + /** + * @see IResourceTree#deletedFile(IFile) + */ + @Override + public void deletedFile(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!file.exists()) + return; + try { + // Delete properties, generate marker deltas, and remove the node from the workspace tree. + ((Resource) file).deleteResource(true, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorDeleting, file.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, file.getFullPath(), message, e); + failed(status); + } + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#deletedFolder(IFolder) + */ + @Override + public void deletedFolder(IFolder folder) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!folder.exists()) + return; + try { + // Delete properties, generate marker deltas, and remove the node from the workspace tree. + ((Resource) folder).deleteResource(true, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorDeleting, folder.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, folder.getFullPath(), message, e); + failed(status); + } + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#deletedProject(IProject) + */ + @Override + public void deletedProject(IProject target) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!target.exists()) + return; + // Delete properties, generate marker deltas, and remove the node from the workspace tree. + try { + ((Project) target).deleteResource(false, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorDeleting, target.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, target.getFullPath(), message, e); + // log the status but don't return until we try and delete the rest of the project info + failed(status); + } + } finally { + lock.release(); + } + } + + /** + * Makes sure that the destination directory for a project move is unoccupied. + * Returns true if successful, and false if the move should be aborted + */ + private boolean ensureDestinationEmpty(IProject source, IFileStore destinationStore, IProgressMonitor monitor) throws CoreException { + String message; + //Make sure the destination location is unoccupied + if (!destinationStore.fetchInfo().exists()) + return true; + //check for existing children + if (destinationStore.childNames(EFS.NONE, Policy.subMonitorFor(monitor, 0)).length > 0) { + //allow case rename to proceed + if (((Resource) source).getStore().equals(destinationStore)) + return true; + //fail because the destination is occupied + message = NLS.bind(Messages.localstore_resourceExists, destinationStore); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, null); + failed(status); + return false; + } + //delete the destination directory to allow for efficient renaming + destinationStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, 0)); + return true; + } + + /** + * This operation has failed for the given reason. Add it to this + * resource tree's status. + */ + @Override + public void failed(IStatus reason) { + Assert.isLegal(isValid); + multistatus.add(reason); + } + + /** + * Returns the status object held onto by this resource tree. + */ + protected IStatus getStatus() { + return multistatus; + } + + /** + * @see IResourceTree#getTimestamp(IFile) + */ + @Override + public long getTimestamp(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + if (!file.exists()) + return NULL_TIMESTAMP; + ResourceInfo info = ((File) file).getResourceInfo(false, false); + return info == null ? NULL_TIMESTAMP : info.getLocalSyncInfo(); + } finally { + lock.release(); + } + } + + /** + * Returns the local timestamp for a file. + * + * @param file + * @return The local file system timestamp + */ + private long internalComputeTimestamp(IFile file) { + IFileInfo fileInfo = localManager.getStore(file).fetchInfo(); + return fileInfo.exists() ? fileInfo.getLastModified() : NULL_TIMESTAMP; + } + + /** + * Helper method for #standardDeleteFile. Returns a boolean indicating whether or + * not the delete was successful. + */ + private boolean internalDeleteFile(IFile file, int flags, IProgressMonitor monitor) { + try { + String message = NLS.bind(Messages.resources_deleting, file.getFullPath()); + monitor.beginTask(message, Policy.totalWork); + Policy.checkCanceled(monitor); + + // Do nothing if the file doesn't exist in the workspace. + if (!file.exists()) { + // Indicate that the delete was successful. + return true; + } + // Don't delete contents if this is a linked resource + if (file.isLinked()) { + deletedFile(file); + return true; + } + // If the file doesn't exist on disk then signal to the workspace to delete the + // file and return. + IFileStore fileStore = localManager.getStore(file); + boolean localExists = fileStore.fetchInfo().exists(); + if (!localExists) { + deletedFile(file); + // Indicate that the delete was successful. + return true; + } + + boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0; + boolean force = (flags & IResource.FORCE) != 0; + + // Add the file to the local history if requested by the user. + if (keepHistory) + addToLocalHistory(file); + monitor.worked(Policy.totalWork / 4); + + // We want to fail if force is false and the file is not synchronized with the + // local file system. + if (!force) { + boolean inSync = isSynchronized(file, IResource.DEPTH_ZERO); + // only want to fail if the file still exists. + if (!inSync && localExists) { + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, file.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, file.getFullPath(), message); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + } + monitor.worked(Policy.totalWork / 4); + + // Try to delete the file from the file system. + try { + fileStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork / 4)); + // If the file was successfully deleted from the file system the + // workspace tree should be updated accordingly. + deletedFile(file); + // Indicate that the delete was successful. + return true; + } catch (CoreException e) { + message = NLS.bind(Messages.resources_couldnotDelete, fileStore.toString()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message, e); + failed(status); + } + // Indicate that the delete was unsuccessful. + return false; + } finally { + monitor.done(); + } + } + + /** + * Helper method for #standardDeleteFolder. Returns a boolean indicating + * whether or not the deletion of this folder was successful. Does a best effort + * delete of this resource and its children. + */ + private boolean internalDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) { + String message = NLS.bind(Messages.resources_deleting, folder.getFullPath()); + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + monitor.subTask(message); + Policy.checkCanceled(monitor); + + // Do nothing if the folder doesn't exist in the workspace. + if (!folder.exists()) + return true; + + // Don't delete contents if this is a linked resource + if (folder.isLinked()) { + deletedFolder(folder); + return true; + } + + // If the folder doesn't exist on disk then update the tree and return. + IFileStore fileStore = localManager.getStore(folder); + if (!fileStore.fetchInfo().exists()) { + deletedFolder(folder); + return true; + } + + try { + //this will delete local and workspace + localManager.delete(folder, flags, Policy.subMonitorFor(monitor, Policy.totalWork)); + } catch (CoreException ce) { + message = NLS.bind(Messages.localstore_couldnotDelete, folder.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, folder.getFullPath(), message, ce); + failed(status); + return false; + } + return true; + } + + /** + * Does a best-effort delete on this resource and all its children. + */ + private boolean internalDeleteProject(IProject project, int flags, IProgressMonitor monitor) { + // Recursively delete each member of the project. + IResource[] members = null; + try { + members = project.members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMembers, project.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, project.getFullPath(), message, e); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + boolean deletedChildren = true; + for (int i = 0; i < members.length; i++) { + IResource child = members[i]; + switch (child.getType()) { + case IResource.FILE : + // ignore the .project file for now and delete it last + if (!IProjectDescription.DESCRIPTION_FILE_NAME.equals(child.getName())) + deletedChildren &= internalDeleteFile((IFile) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length)); + break; + case IResource.FOLDER : + deletedChildren &= internalDeleteFolder((IFolder) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length)); + break; + } + } + IFileStore projectStore = localManager.getStore(project); + // Check to see if the children were deleted ok. If there was a problem + // just return as the problem should have been logged by the recursive + // call to the child. + if (!deletedChildren) + // Indicate that the delete was unsuccessful. + return false; + + //Check if there are any undiscovered children of the project on disk other than description file + String[] children; + try { + children = projectStore.childNames(EFS.NONE, null); + } catch (CoreException e) { + //treat failure to access the directory as a non-existent directory + children = new String[0]; + } + boolean force = BitMask.isSet(flags, IResource.FORCE); + if (!force && (children.length != 1 || !IProjectDescription.DESCRIPTION_FILE_NAME.equals(children[0]))) { + String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, project.getName()); + failed(new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, project.getFullPath(), message)); + return false; + } + + //Now delete the project description file + IResource file = project.findMember(IProjectDescription.DESCRIPTION_FILE_NAME); + if (file == null) { + //the .project have may have been recreated on disk automatically by snapshot + IFileStore dotProject = projectStore.getChild(IProjectDescription.DESCRIPTION_FILE_NAME); + try { + dotProject.delete(EFS.NONE, null); + } catch (CoreException e) { + failed(e.getStatus()); + } + } else { + boolean deletedProjectFile = internalDeleteFile((IFile) file, flags, Policy.monitorFor(null)); + if (!deletedProjectFile) { + String message = NLS.bind(Messages.resources_couldnotDelete, file.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + } + + //children are deleted, so now delete the parent + try { + projectStore.delete(EFS.NONE, null); + deletedProject(project); + // Indicate that the delete was successful. + return true; + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_couldnotDelete, projectStore.toString()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message, e); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + } + + /** + * Return true if there is a change in the content area for the project. + */ + private boolean isContentChange(IProject project, IProjectDescription destDescription) { + IProjectDescription srcDescription = ((Project) project).internalGetDescription(); + URI srcLocation = srcDescription.getLocationURI(); + URI destLocation = destDescription.getLocationURI(); + if (srcLocation == null || destLocation == null) + return true; + //don't use URIUtil because we want to treat case rename as a content change + return !srcLocation.equals(destLocation); + } + + /** + * Return true if there is a change in the name of the project. + */ + private boolean isNameChange(IProject project, IProjectDescription description) { + return !project.getName().equals(description.getName()); + } + + /** + * Refreshes the resource hierarchy with its children. In case of failure + * adds an appropriate status to the resource tree's status. + */ + private void safeRefresh(IResource resource) { + try { + resource.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); + } catch (CoreException ce) { + IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, resource.getFullPath(), Messages.refresh_refreshErr, ce); + failed(status); + } + } + + /** + * @see IResourceTree#isSynchronized(IResource, int) + */ + @Override + public boolean isSynchronized(IResource resource, int depth) { + try { + lock.acquire(); + return localManager.isSynchronized(resource, depth); + } finally { + lock.release(); + } + } + + /** + * The specific operation for which this tree was created has completed and this tree + * should not be used anymore. Ensure that this is the case by making it invalid. This + * is checked by all API methods. + */ + void makeInvalid() { + this.isValid = false; + } + + /** + * @see IResourceTree#movedFile(IFile, IFile) + */ + @Override + public void movedFile(IFile source, IFile destination) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!source.exists()) + return; + // If the destination already exists then we have a problem. + if (destination.exists()) { + String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); + // log the status but don't return until we try and move the rest of the resource information. + failed(status); + } + + // Move the resource's persistent properties. + IPropertyManager propertyManager = ((Resource) source).getPropertyManager(); + try { + propertyManager.copy(source, destination, IResource.DEPTH_ZERO); + propertyManager.deleteProperties(source, IResource.DEPTH_ZERO); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource information. + failed(status); + } + + // Move the node in the workspace tree. + Workspace workspace = (Workspace) source.getWorkspace(); + try { + workspace.move((Resource) source, destination.getFullPath(), IResource.DEPTH_ZERO, updateFlags, false); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource information. + failed(status); + } + + // Generate the marker deltas. + try { + workspace.getMarkerManager().moved(source, destination, IResource.DEPTH_ZERO); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + } + + // Copy the local history information + copyLocalHistory(source, destination); + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#movedFolderSubtree(IFolder, IFolder) + */ + @Override + public void movedFolderSubtree(IFolder source, IFolder destination) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the source resource doesn't exist. + if (!source.exists()) + return; + // If the destination already exists then we have an error. + if (destination.exists()) { + String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); + failed(status); + return; + } + + // Move the folder properties. + int depth = IResource.DEPTH_INFINITE; + IPropertyManager propertyManager = ((Resource) source).getPropertyManager(); + try { + propertyManager.copy(source, destination, depth); + propertyManager.deleteProperties(source, depth); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Create the destination node in the tree. + Workspace workspace = (Workspace) source.getWorkspace(); + try { + workspace.move((Resource) source, destination.getFullPath(), depth, updateFlags, false); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Generate the marker deltas. + try { + workspace.getMarkerManager().moved(source, destination, depth); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + } + + // Copy the local history for this folder + copyLocalHistory(source, destination); + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#movedProjectSubtree(IProject, IProjectDescription) + */ + @Override + public boolean movedProjectSubtree(IProject project, IProjectDescription destDescription) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the source resource doesn't exist. + if (!project.exists()) + return true; + + Project source = (Project) project; + Project destination = (Project) source.getWorkspace().getRoot().getProject(destDescription.getName()); + Workspace workspace = (Workspace) source.getWorkspace(); + int depth = IResource.DEPTH_INFINITE; + + // If the name of the source and destination projects are not the same then + // rename the meta area and make changes in the tree. + if (isNameChange(source, destDescription)) { + if (destination.exists()) { + String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); + failed(status); + return false; + } + + // Rename the project metadata area. Close the property store to flush everything to disk + try { + source.getPropertyManager().closePropertyStore(source); + localManager.getHistoryStore().closeHistoryStore(source); + } catch (CoreException e) { + String message = NLS.bind(Messages.properties_couldNotClose, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + final IFileSystem fileSystem = EFS.getLocalFileSystem(); + IFileStore oldMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(source)); + IFileStore newMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(destination)); + try { + oldMetaArea.move(newMetaArea, EFS.NONE, new NullProgressMonitor()); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_moveMeta, oldMetaArea, newMetaArea); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_WRITE_METADATA, destination.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Move the workspace tree. + try { + workspace.move(source, destination.getFullPath(), depth, updateFlags, true); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Clear stale state on the destination project. + ((ProjectInfo) destination.getResourceInfo(false, true)).fixupAfterMove(); + + // Generate marker deltas. + try { + workspace.getMarkerManager().moved(source, destination, depth); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMarkersMove, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + // Copy the local history + copyLocalHistory(source, destination); + } + + // Write the new project description on the destination project. + try { + //moving linked resources may have modified the description in memory + ((ProjectDescription) destDescription).setLinkDescriptions(destination.internalGetDescription().getLinks()); + // moving filters may have modified the description in memory + ((ProjectDescription) destDescription).setFilterDescriptions(destination.internalGetDescription().getFilters()); + // moving variables may have modified the description in memory + ((ProjectDescription) destDescription).setVariableDescriptions(destination.internalGetDescription().getVariables()); + destination.internalSetDescription(destDescription, true); + destination.writeDescription(IResource.FORCE); + } catch (CoreException e) { + String message = Messages.resources_projectDesc; + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e); + failed(status); + } + + // write the private project description, including the project location + try { + workspace.getMetaArea().writePrivateDescription(destination); + } catch (CoreException e) { + failed(e.getStatus()); + } + + // Do a refresh on the destination project to pick up any newly discovered resources + try { + destination.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorRefresh, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e); + failed(status); + return false; + } + return true; + } finally { + lock.release(); + } + } + + /** + * Helper method for moving the project content. Determines the content location + * based on the project description. (default location or user defined?) + */ + private void moveProjectContent(IProject source, IFileStore destStore, int flags, IProgressMonitor monitor) throws CoreException { + try { + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.beginTask(message, 10); + IProjectDescription srcDescription = source.getDescription(); + URI srcLocation = srcDescription.getLocationURI(); + // If the locations are the same (and non-default) then there is nothing to do. + if (srcLocation != null && URIUtil.equals(srcLocation, destStore.toURI())) + return; + + //If this is a replace, just make sure the destination location exists, and return + boolean replace = (flags & IResource.REPLACE) != 0; + if (replace) { + destStore.mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 10)); + return; + } + + // Move the contents on disk. + localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 9)); + + //if this is a deep move, move the contents of any linked resources + if ((flags & IResource.SHALLOW) == 0) { + IResource[] children = source.members(); + for (int i = 0; i < children.length; i++) { + if (children[i].isLinked()) { + message = NLS.bind(Messages.resources_moving, children[i].getFullPath()); + monitor.subTask(message); + IFileStore linkDestination = destStore.getChild(children[i].getName()); + try { + localManager.move(children[i], linkDestination, flags, Policy.monitorFor(null)); + } catch (CoreException ce) { + //log the failure, but keep trying on remaining links + failed(ce.getStatus()); + } + } + } + } + monitor.worked(1); + } finally { + monitor.done(); + } + } + + /** + * @see IResourceTree#standardDeleteFile(IFile, int, IProgressMonitor) + */ + @Override + public void standardDeleteFile(IFile file, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + internalDeleteFile(file, flags, monitor); + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#standardDeleteFolder(IFolder, int, IProgressMonitor) + */ + @Override + public void standardDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + internalDeleteFolder(folder, flags, monitor); + } catch (OperationCanceledException oce) { + safeRefresh(folder); + throw oce; + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardDeleteProject(IProject, int, IProgressMonitor) + */ + @Override + public void standardDeleteProject(IProject project, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_deleting, project.getFullPath()); + monitor.beginTask(message, Policy.totalWork); + // Do nothing if the project doesn't exist in the workspace tree. + if (!project.exists()) + return; + + boolean alwaysDeleteContent = (flags & IResource.ALWAYS_DELETE_PROJECT_CONTENT) != 0; + boolean neverDeleteContent = (flags & IResource.NEVER_DELETE_PROJECT_CONTENT) != 0; + boolean success = true; + + // Delete project content. Don't do anything if the user specified explicitly asked + // not to delete the project content or if the project is closed and + // ALWAYS_DELETE_PROJECT_CONTENT was not specified. + if (alwaysDeleteContent || (project.isOpen() && !neverDeleteContent)) { + // Force is implied if alwaysDeleteContent is true or if the project is in sync + // with the local file system. + if (alwaysDeleteContent || isSynchronized(project, IResource.DEPTH_INFINITE)) { + flags |= IResource.FORCE; + } + + // If the project is open we have to recursively try and delete all the files doing best-effort. + if (project.isOpen()) { + success = internalDeleteProject(project, flags, monitor); + if (!success) { + IFileStore store = localManager.getStore(project); + message = NLS.bind(Messages.resources_couldnotDelete, store.toString()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message); + failed(status); + } + return; + } + + // If the project is closed we can short circuit this operation and delete all the files on disk. + // The .project file is deleted at the end of the operation. + try { + IFileStore projectStore = localManager.getStore(project); + IFileStore members[] = projectStore.childStores(EFS.NONE, null); + for (int i = 0; i < members.length; i++) { + if (!IProjectDescription.DESCRIPTION_FILE_NAME.equals(members[i].getName())) + members[i].delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8 / members.length)); + } + projectStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8 / (members.length > 0 ? members.length : 1))); + } catch (OperationCanceledException oce) { + safeRefresh(project); + throw oce; + } catch (CoreException ce) { + message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message, ce); + failed(status); + return; + } + } + + // Signal that the workspace tree should be updated that the project has been deleted. + if (success) + deletedProject(project); + else { + message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message); + failed(status); + } + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardMoveFile(IFile, IFile, int, IProgressMonitor) + */ + @Override + public void standardMoveFile(IFile source, IFile destination, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.subTask(message); + + // These pre-conditions should all be ok but just in case... + if (!source.exists() || destination.exists() || !destination.getParent().isAccessible()) + throw new IllegalArgumentException(); + + boolean force = (flags & IResource.FORCE) != 0; + boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0; + boolean isDeep = (flags & IResource.SHALLOW) == 0; + + // If the file is not in sync with the local file system and force is false, + // then signal that we have an error. + if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message); + failed(status); + return; + } + monitor.worked(Policy.totalWork / 4); + + // Add the file contents to the local history if requested by the user. + if (keepHistory) + addToLocalHistory(source); + monitor.worked(Policy.totalWork / 4); + + //for shallow move of linked resources, nothing needs to be moved in the file system + if (!isDeep && source.isLinked()) { + movedFile(source, destination); + return; + } + + // If the file was successfully moved in the file system then the workspace + // tree needs to be updated accordingly. Otherwise signal that we have an error. + IFileStore destStore = null; + boolean failedDeletingSource = false; + try { + destStore = localManager.getStore(destination); + //ensure parent of destination exists + destStore.getParent().mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 0)); + localManager.move(source, destStore, flags, monitor); + } catch (CoreException e) { + failed(e.getStatus()); + // did the fail occur after copying to the destination? + failedDeletingSource = destStore != null && destStore.fetchInfo().exists(); + // if so, we should proceed + if (!failedDeletingSource) + return; + } + movedFile(source, destination); + updateMovedFileTimestamp(destination, internalComputeTimestamp(destination)); + if (failedDeletingSource) { + //recreate source file to ensure we are not out of sync + try { + source.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e) { + //ignore secondary failure - we have already logged the main failure + } + } + monitor.worked(Policy.totalWork / 4); + return; + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardMoveFolder(IFolder, IFolder, int, IProgressMonitor) + */ + @Override + public void standardMoveFolder(IFolder source, IFolder destination, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.beginTask(message, 100); + + // These pre-conditions should all be ok but just in case... + if (!source.exists() || destination.exists() || !destination.getParent().isAccessible()) + throw new IllegalArgumentException(); + + // Check to see if we are synchronized with the local file system. If we are in sync then we can + // short circuit this method and do a file system only move. Otherwise we have to recursively + // try and move all resources, doing it in a best-effort manner. + boolean force = (flags & IResource.FORCE) != 0; + if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message); + failed(status); + return; + } + monitor.worked(20); + + //for linked resources, nothing needs to be moved in the file system + boolean isDeep = (flags & IResource.SHALLOW) == 0; + if (!isDeep && (source.isLinked() || source.isVirtual())) { + movedFolderSubtree(source, destination); + return; + } + + // Move the resources in the file system. Only the FORCE flag is valid here so don't + // have to worry about clearing the KEEP_HISTORY flag. + IFileStore destStore = null; + boolean failedDeletingSource = false; + try { + destStore = localManager.getStore(destination); + localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 60)); + } catch (CoreException e) { + failed(e.getStatus()); + // did the fail occur after copying to the destination? + failedDeletingSource = destStore != null && destStore.fetchInfo().exists(); + // if so, we should proceed + if (!failedDeletingSource) + return; + } + movedFolderSubtree(source, destination); + monitor.worked(20); + updateTimestamps(destination, isDeep); + if (failedDeletingSource) { + //the move could have been partially successful, so refresh to ensure we are in sync + try { + source.refreshLocal(IResource.DEPTH_INFINITE, null); + destination.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e) { + //ignore secondary failures -we have already logged main failure + } + } + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardMoveProject(IProject, IProjectDescription, int, IProgressMonitor) + */ + @Override + public void standardMoveProject(IProject source, IProjectDescription description, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.beginTask(message, Policy.totalWork); + + // Double-check this pre-condition. + if (!source.isAccessible()) + throw new IllegalArgumentException(); + + // If there is nothing to do on disk then signal to make the workspace tree + // changes. + if (!isContentChange(source, description)) { + movedProjectSubtree(source, description); + return; + } + + // Check to see if we are synchronized with the local file system. + boolean force = (flags & IResource.FORCE) != 0; + if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { + // FIXME: make this a best effort move? + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message); + failed(status); + return; + } + + IFileStore destinationStore; + try { + destinationStore = computeDestinationStore(description); + //destination can be non-empty on replace + if ((flags & IResource.REPLACE) == 0) + if (!ensureDestinationEmpty(source, destinationStore, monitor)) + return; + } catch (CoreException e) { + //must fail if the destination location cannot be accessd (undefined file system) + message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + return; + } + + // Move the project content in the local file system. + try { + moveProjectContent(source, destinationStore, flags, Policy.subMonitorFor(monitor, Policy.totalWork * 3 / 4)); + } catch (CoreException e) { + message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + //refresh the project because it might have been partially moved + try { + source.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e2) { + //ignore secondary failures + } + } + + // If we got this far the project content has been moved on disk (if necessary) + // and we need to update the workspace tree. + movedProjectSubtree(source, description); + monitor.worked(Policy.totalWork * 1 / 8); + + boolean isDeep = (flags & IResource.SHALLOW) == 0; + updateTimestamps(source.getWorkspace().getRoot().getProject(description.getName()), isDeep); + monitor.worked(Policy.totalWork * 1 / 8); + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#updateMovedFileTimestamp(IFile, long) + */ + @Override + public void updateMovedFileTimestamp(IFile file, long timestamp) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the file doesn't exist in the workspace tree. + if (!file.exists()) + return; + // Update the timestamp in the tree. + ResourceInfo info = ((Resource) file).getResourceInfo(false, true); + // The info should never be null since we just checked that the resource exists in the tree. + localManager.updateLocalSync(info, timestamp); + //remove the linked bit since this resource has been moved in the file system + info.clear(ICoreConstants.M_LINK); + } finally { + lock.release(); + } + } + + /** + * Helper method to update all the timestamps in the tree to match + * those in the file system. Used after a #move. + */ + private void updateTimestamps(IResource root, final boolean isDeep) { + IResourceVisitor visitor = new IResourceVisitor() { + @Override + public boolean visit(IResource resource) { + if (resource.isLinked()) { + if (isDeep && !((Resource) resource).isUnderVirtual()) { + //clear the linked resource bit, if any + ResourceInfo info = ((Resource) resource).getResourceInfo(false, true); + info.clear(ICoreConstants.M_LINK); + } + return true; + } + //only needed if underlying file system does not preserve timestamps + // if (resource.getType() == IResource.FILE) { + // IFile file = (IFile) resource; + // updateMovedFileTimestamp(file, computeTimestamp(file)); + // } + return true; + } + }; + try { + root.accept(visitor, IResource.DEPTH_INFINITE, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + } catch (CoreException e) { + // No exception should be thrown. + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java new file mode 100644 index 0000000000..674b3e41cf --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * 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.runtime.QualifiedName; + +public class RootInfo extends ResourceInfo { + /** The property store for this resource */ + protected Object propertyStore = null; + + /** + * Returns the property store associated with this info. The return value may be null. + */ + @Override + public Object getPropertyStore() { + return propertyStore; + } + + /** + * Override parent's behaviour and do nothing. Sync information + * cannot be stored on the workspace root so we don't need to + * update this counter which is used for deltas. + */ + @Override + public void incrementSyncInfoGenerationCount() { + // do nothing + } + + /** + * Sets the property store associated with this info. The value may be null. + */ + @Override + public void setPropertyStore(Object value) { + propertyStore = value; + } + + /** + * Overrides parent's behaviour since sync information is not + * stored on the workspace root. + */ + @Override + public void setSyncInfo(QualifiedName id, byte[] value) { + // do nothing + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java new file mode 100644 index 0000000000..f8e3e63906 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java @@ -0,0 +1,232 @@ +/******************************************************************************* + * 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 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.internal.events.ILifecycleListener; +import org.eclipse.core.internal.events.LifecycleEvent; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.ResourceRuleFactory; +import org.eclipse.core.resources.team.TeamHook; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; + +/** + * Class for calculating scheduling rules for resource changing operations. + * This factory delegates to the TeamHook to obtain an appropriate factory + * for the resource that the operation is proposing to modify. + */ +class Rules implements IResourceRuleFactory, ILifecycleListener { + private final ResourceRuleFactory defaultFactory = new ResourceRuleFactory() {/**/}; + /** + * Map of project names to the factory for that project. + */ + private final Map projectsToRules = Collections.synchronizedMap(new HashMap()); + private final TeamHook teamHook; + private final IWorkspaceRoot root; + + /** + * Creates a new scheduling rule factory for the given workspace + * @param workspace + */ + Rules(Workspace workspace) { + this.root = workspace.getRoot(); + this.teamHook = workspace.getTeamHook(); + workspace.addLifecycleListener(this); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a build operation. + */ + @Override + public ISchedulingRule buildRule() { + //team hook currently cannot change this rule + return root; + } + + /** + * Obtains the scheduling rule from the appropriate factories for a copy operation. + */ + @Override + public ISchedulingRule copyRule(IResource source, IResource destination) { + if (source.getType() == IResource.ROOT || destination.getType() == IResource.ROOT) + return root; + //source is not modified, destination is created + return factoryFor(destination).copyRule(source, destination); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a create operation. + */ + @Override + public ISchedulingRule createRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).createRule(resource); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a delete operation. + */ + @Override + public ISchedulingRule deleteRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).deleteRule(resource); + } + + /** + * Returns the scheduling rule factory for the given resource + */ + private IResourceRuleFactory factoryFor(IResource destination) { + IResourceRuleFactory fac = projectsToRules.get(destination.getFullPath().segment(0)); + if (fac == null) { + //use the default factory if the project is not yet accessible + if (!destination.getProject().isAccessible()) + return defaultFactory; + //ask the team hook to supply one + fac = teamHook.getRuleFactory(destination.getProject()); + projectsToRules.put(destination.getFullPath().segment(0), fac); + } + return fac; + } + + /* (non-Javadoc) + * @see org.eclipse.core.internal.events.ILifecycleListener#handleEvent(org.eclipse.core.internal.events.LifecycleEvent) + */ + @Override + public void handleEvent(LifecycleEvent event) { + //clear resource rule factory for projects that are about to be closed + //or deleted. It is ok to do this during a PRE event because the rule + //has already been obtained at this point. + switch (event.kind) { + case LifecycleEvent.PRE_PROJECT_CLOSE : + case LifecycleEvent.PRE_PROJECT_DELETE : + case LifecycleEvent.PRE_PROJECT_MOVE : + setRuleFactory((IProject) event.resource, null); + } + } + + /** + * Obtains the scheduling rule from the appropriate factory for a charset change operation. + */ + @Override + public ISchedulingRule charsetRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return null; + return factoryFor(resource).charsetRule(resource); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a derived flag change operation. + */ + @Override + public ISchedulingRule derivedRule(IResource resource) { + //team hook currently cannot change this rule + return null; + } + + /** + * Obtains the scheduling rule from the appropriate factory for a marker change operation. + */ + @Override + public ISchedulingRule markerRule(IResource resource) { + //team hook currently cannot change this rule + return null; + } + + /** + * Obtains the scheduling rule from the appropriate factory for a sync info change operation. + */ + @Override + public ISchedulingRule syncInfoRule(IResource resource) { + //team hook currently cannot change this rule + return null; + } + + /** + * Obtains the scheduling rule from the appropriate factory for a modify operation. + */ + @Override + public ISchedulingRule modifyRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).modifyRule(resource); + } + + /** + * Obtains the scheduling rule from the appropriate factories for a move operation. + */ + @Override + public ISchedulingRule moveRule(IResource source, IResource destination) { + if (source.getType() == IResource.ROOT || destination.getType() == IResource.ROOT) + return root; + //treat a move across projects as a create on the destination and a delete on the source + if (!source.getFullPath().segment(0).equals(destination.getFullPath().segment(0))) + return MultiRule.combine(modifyRule(source.getProject()), modifyRule(destination.getProject())); + return factoryFor(source).moveRule(source, destination); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a refresh operation. + */ + @Override + public ISchedulingRule refreshRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).refreshRule(resource); + } + + /* (non-javadoc) + * Implements TeamHook#setRuleFactory + */ + void setRuleFactory(IProject project, IResourceRuleFactory factory) { + if (factory == null) + projectsToRules.remove(project.getName()); + else + projectsToRules.put(project.getName(), factory); + } + + /** + * Combines rules for each parameter to validateEdit from the corresponding + * rule factories. + */ + @Override + public ISchedulingRule validateEditRule(IResource[] resources) { + if (resources.length == 0) + return null; + //optimize rule for single file + if (resources.length == 1) { + if (resources[0].getType() == IResource.ROOT) + return root; + return factoryFor(resources[0]).validateEditRule(resources); + } + //gather rules for each resource from appropriate factory + HashSet rules = new HashSet(); + IResource[] oneResource = new IResource[1]; + for (int i = 0; i < resources.length; i++) { + if (resources[i].getType() == IResource.ROOT) + return root; + oneResource[0] = resources[i]; + ISchedulingRule rule = factoryFor(resources[i]).validateEditRule(oneResource); + if (rule != null) + rules.add(rule); + } + if (rules.isEmpty()) + return null; + if (rules.size() == 1) + return rules.iterator().next(); + ISchedulingRule[] ruleArray = rules.toArray(new ISchedulingRule[rules.size()]); + return new MultiRule(ruleArray); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java new file mode 100644 index 0000000000..2e31b46e2e --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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.resources; + +import java.io.*; +import java.util.Properties; +import java.util.Set; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; + +/** + * Represents a table of keys and paths used by a plugin to maintain its + * configuration files' names. + */ +public class SafeFileTable { + protected IPath location; + protected Properties table; + + public SafeFileTable(String pluginId) throws CoreException { + location = getWorkspace().getMetaArea().getSafeTableLocationFor(pluginId); + restore(); + } + + public IPath[] getFiles() { + Set set = table.keySet(); + String[] keys = set.toArray(new String[set.size()]); + IPath[] files = new IPath[keys.length]; + for (int i = 0; i < keys.length; i++) + files[i] = new Path(keys[i]); + return files; + } + + protected Workspace getWorkspace() { + return (Workspace) ResourcesPlugin.getWorkspace(); + } + + public IPath lookup(IPath file) { + String result = table.getProperty(file.toOSString()); + return result == null ? null : new Path(result); + } + + public void map(IPath file, IPath aLocation) { + if (aLocation == null) + table.remove(file); + else + table.setProperty(file.toOSString(), aLocation.toOSString()); + } + + public void restore() throws CoreException { + java.io.File target = location.toFile(); + table = new Properties(); + if (!target.exists()) + return; + try { + FileInputStream input = new FileInputStream(target); + try { + table.load(input); + } finally { + input.close(); + } + } catch (IOException e) { + String message = Messages.resources_exSafeRead; + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); + } + } + + public void save() throws CoreException { + java.io.File target = location.toFile(); + try { + FileOutputStream output = new FileOutputStream(target); + try { + table.store(output, "safe table"); //$NON-NLS-1$ + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (IOException e) { + String message = Messages.resources_exSafeSave; + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); + } + } + + public void setLocation(IPath location) { + if (location != null) + this.location = location; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java new file mode 100644 index 0000000000..2185abba04 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; + +public class SaveContext implements ISaveContext { + protected String pluginId; + protected int kind; + protected boolean needDelta; + protected boolean needSaveNumber; + protected SafeFileTable fileTable; + protected int previousSaveNumber; + protected IProject project; + + protected SaveContext(String pluginId, int kind, IProject project) throws CoreException { + this.kind = kind; + this.project = project; + this.pluginId = pluginId; + needDelta = false; + needSaveNumber = false; + fileTable = new SafeFileTable(pluginId); + previousSaveNumber = getWorkspace().getSaveManager().getSaveNumber(pluginId); + } + + public void commit() throws CoreException { + if (needSaveNumber) { + IPath oldLocation = getWorkspace().getMetaArea().getSafeTableLocationFor(pluginId); + getWorkspace().getSaveManager().setSaveNumber(pluginId, getSaveNumber()); + fileTable.setLocation(getWorkspace().getMetaArea().getSafeTableLocationFor(pluginId)); + fileTable.save(); + oldLocation.toFile().delete(); + } + } + + /** + * @see ISaveContext + */ + @Override + public IPath[] getFiles() { + return getFileTable().getFiles(); + } + + protected SafeFileTable getFileTable() { + return fileTable; + } + + /** + * @see ISaveContext + */ + @Override + public int getKind() { + return kind; + } + + public String getPluginId() { + return pluginId; + } + + /** + * @see ISaveContext + */ + @Override + public int getPreviousSaveNumber() { + return previousSaveNumber; + } + + /** + * @see ISaveContext + */ + @Override + public IProject getProject() { + return project; + } + + /** + * @see ISaveContext + */ + @Override + public int getSaveNumber() { + int result = getPreviousSaveNumber() + 1; + return result > 0 ? result : 1; + } + + protected Workspace getWorkspace() { + return (Workspace) ResourcesPlugin.getWorkspace(); + } + + public boolean isDeltaNeeded() { + return needDelta; + } + + /** + * @see ISaveContext + */ + @Override + public IPath lookup(IPath file) { + return getFileTable().lookup(file); + } + + /** + * @see ISaveContext + */ + @Override + public void map(IPath file, IPath location) { + getFileTable().map(file, location); + } + + /** + * @see ISaveContext + */ + @Override + public void needDelta() { + needDelta = true; + } + + /** + * @see ISaveContext + */ + @Override + public void needSaveNumber() { + needSaveNumber = true; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java new file mode 100644 index 0000000000..2af73ed292 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java @@ -0,0 +1,2140 @@ +/******************************************************************************* + * 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 + * Francis Lynch (Wind River) - [301563] Save and load tree snapshots + * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project + * Baltasar Belyavsky (Texas Instruments) - [361675] Order mismatch when saving/restoring workspace trees + * 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.io.File; +import java.net.URI; +import java.util.*; +import java.util.zip.*; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.internal.events.*; +import org.eclipse.core.internal.localstore.*; +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.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; + +public class SaveManager implements IElementInfoFlattener, IManager, IStringPoolParticipant { + class MasterTable extends Properties { + private static final long serialVersionUID = 1L; + + @Override + public synchronized Object put(Object key, Object value) { + Object prev = super.put(key, value); + if (prev != null && ROOT_SEQUENCE_NUMBER_KEY.equals(key)) { + int prevSeqNum = new Integer((String) prev).intValue(); + int currSeqNum = new Integer((String) value).intValue(); + if (prevSeqNum > currSeqNum) { + //revert last put operation + super.put(key, prev); + //notify about the problem, do not throw exception but add the exception to know where it occurred + String message = "Cannot set lower sequence number for root (previous: " + prevSeqNum + ", new: " + currSeqNum + "). Ignoring the new value."; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Policy.log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, new IllegalArgumentException(message))); + } + } + return prev; + } + } + + protected static final String ROOT_SEQUENCE_NUMBER_KEY = Path.ROOT.toString() + LocalMetaArea.F_TREE; + protected static final String CLEAR_DELTA_PREFIX = "clearDelta_"; //$NON-NLS-1$ + protected static final String DELTA_EXPIRATION_PREFIX = "deltaExpiration_"; //$NON-NLS-1$ + protected static final int DONE_SAVING = 3; + + /** + * The minimum delay, in milliseconds, between workspace snapshots + */ + private static final long MIN_SNAPSHOT_DELAY = 1000 * 30L; //30 seconds + + /** + * The number of empty operations that are equivalent to a single non- + * trivial operation. + */ + protected static final int NO_OP_THRESHOLD = 20; + + /** constants */ + protected static final int PREPARE_TO_SAVE = 1; + protected static final int ROLLBACK = 4; + protected static final String SAVE_NUMBER_PREFIX = "saveNumber_"; //$NON-NLS-1$ + protected static final int SAVING = 2; + protected ElementTree lastSnap; + protected MasterTable masterTable; + + /** + * A flag indicating that a save operation is occurring. This is a signal + * that snapshot should not be scheduled if a nested operation occurs during + * save. + */ + private boolean isSaving = false; + + /** + * The number of empty (non-changing) operations since the last snapshot. + */ + protected int noopCount = 0; + /** + * The number of non-trivial operations since the last snapshot. + */ + protected int operationCount = 0; + + // Count up the time taken for all saves/snaps on markers and sync info + protected long persistMarkers = 0l; + protected long persistSyncInfo = 0l; + + /** + * In-memory representation of plugins saved state. Maps String (plugin id)-> SavedState. + * This map is accessed from API that is not synchronized, so it requires + * independent synchronization. This is accomplished using a synchronized + * wrapper map. + */ + protected Map savedStates; + + /** + * Ids of plugins that participate on a workspace save. Maps String (plugin id)-> ISaveParticipant. + * This map is accessed from API that is not synchronized, so it requires + * independent synchronization. This is accomplished using a synchronized + * wrapper map. + */ + protected Map saveParticipants; + + protected final DelayedSnapshotJob snapshotJob; + + protected boolean snapshotRequested; + private IStatus snapshotRequestor; + protected Workspace workspace; + //declare debug messages as fields to get sharing + private static final String DEBUG_START = " starting..."; //$NON-NLS-1$ + private static final String DEBUG_FULL_SAVE = "Full save on workspace: "; //$NON-NLS-1$ + private static final String DEBUG_PROJECT_SAVE = "Save on project "; //$NON-NLS-1$ + private static final String DEBUG_SNAPSHOT = "Snapshot: "; //$NON-NLS-1$ + private static final int TREE_BUFFER_SIZE = 1024 * 64;//64KB buffer + + public SaveManager(Workspace workspace) { + this.workspace = workspace; + this.masterTable = new MasterTable(); + this.snapshotJob = new DelayedSnapshotJob(this); + snapshotRequested = false; + snapshotRequestor = null; + saveParticipants = Collections.synchronizedMap(new HashMap(10)); + } + + public ISavedState addParticipant(String pluginId, ISaveParticipant participant) throws CoreException { + // If the plugin was already registered as a save participant we return null + if (saveParticipants.put(pluginId, participant) != null) + return null; + SavedState state = savedStates.get(pluginId); + if (state != null) { + if (isDeltaCleared(pluginId)) { + // this plugin was marked not to receive deltas + state.forgetTrees(); + removeClearDeltaMarks(pluginId); + } else { + try { + // thread safety: (we need to guarantee that the tree is immutable when computing deltas) + // so, the tree inside the saved state needs to be immutable + workspace.prepareOperation(null, null); + workspace.beginOperation(true); + state.newTree = workspace.getElementTree(); + } finally { + workspace.endOperation(null, false, null); + } + return state; + } + } + // if the plug-in has a previous save number, we return a state, otherwise we return null + if (getSaveNumber(pluginId) > 0) + return new SavedState(workspace, pluginId, null, null); + return null; + } + + protected void broadcastLifecycle(final int lifecycle, Map contexts, final MultiStatus warnings, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", contexts.size()); //$NON-NLS-1$ + for (final Iterator> it = contexts.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + String pluginId = entry.getKey(); + final ISaveParticipant participant = saveParticipants.get(pluginId); + //save participants can be removed concurrently + if (participant == null) { + monitor.worked(1); + continue; + } + final SaveContext context = entry.getValue(); + /* Be extra careful when calling lifecycle method on arbitrary plugin */ + ISafeRunnable code = new ISafeRunnable() { + + @Override + public void handleException(Throwable e) { + String message = Messages.resources_saveProblem; + IStatus status = new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, e); + warnings.add(status); + + /* Remove entry for defective plug-in from this save operation */ + it.remove(); + } + + @Override + public void run() throws Exception { + executeLifecycle(lifecycle, participant, context); + } + }; + SafeRunner.run(code); + monitor.worked(1); + } + } finally { + monitor.done(); + } + } + + /** + * Remove the delta expiration timestamp from the master table, either + * because the saved state has been processed, or the delta has expired. + */ + protected void clearDeltaExpiration(String pluginId) { + masterTable.remove(DELTA_EXPIRATION_PREFIX + pluginId); + } + + protected void cleanMasterTable() { + //remove tree file entries for everything except closed projects + for (Iterator it = masterTable.keySet().iterator(); it.hasNext();) { + String key = (String) it.next(); + if (!key.endsWith(LocalMetaArea.F_TREE)) + continue; + String prefix = key.substring(0, key.length() - LocalMetaArea.F_TREE.length()); + //always save the root tree entry + if (prefix.equals(Path.ROOT.toString())) + continue; + IProject project = workspace.getRoot().getProject(prefix); + if (!project.exists() || project.isOpen()) + it.remove(); + } + IPath location = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES); + IPath backup = workspace.getMetaArea().getBackupLocationFor(location); + try { + saveMasterTable(ISaveContext.FULL_SAVE, backup); + } catch (CoreException e) { + Policy.log(e.getStatus()); + backup.toFile().delete(); + return; + } + if (location.toFile().exists() && !location.toFile().delete()) + return; + try { + saveMasterTable(ISaveContext.FULL_SAVE, location); + } catch (CoreException e) { + Policy.log(e.getStatus()); + location.toFile().delete(); + return; + } + backup.toFile().delete(); + } + + /** + * Marks the current participants to not receive deltas next time they are registered + * as save participants. This is done in order to maintain consistency if we crash + * after a snapshot. It would force plug-ins to rebuild their state. + */ + protected void clearSavedDelta() { + synchronized (saveParticipants) { + for (Iterator i = saveParticipants.keySet().iterator(); i.hasNext();) { + String pluginId = i.next(); + masterTable.setProperty(CLEAR_DELTA_PREFIX + pluginId, "true"); //$NON-NLS-1$ + } + } + } + + /** + * Collects the set of ElementTrees we are still interested in, + * and removes references to any other trees. + */ + protected void collapseTrees(Map contexts) throws CoreException { + //collect trees we're interested in + + //forget saved trees, if they are not used by registered participants + synchronized (savedStates) { + for (Iterator i = contexts.values().iterator(); i.hasNext();) { + SaveContext context = i.next(); + forgetSavedTree(context.getPluginId()); + } + } + + //trees for plugin saved states + ArrayList trees = new ArrayList(); + synchronized (savedStates) { + for (Iterator i = savedStates.values().iterator(); i.hasNext();) { + SavedState state = i.next(); + if (state.oldTree != null) { + trees.add(state.oldTree); + } + } + } + + //trees for builders + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) { + IProject project = projects[i]; + if (project.isOpen()) { + ArrayList builderInfos = workspace.getBuildManager().createBuildersPersistentInfo(project); + if (builderInfos != null) { + for (Iterator it = builderInfos.iterator(); it.hasNext();) { + BuilderPersistentInfo info = it.next(); + trees.add(info.getLastBuiltTree()); + } + } + } + } + + //no need to collapse if there are no trees at this point + if (trees.isEmpty()) + return; + + //the complete tree + trees.add(workspace.getElementTree()); + + //collapse the trees + //sort trees in topological order, and set the parent of each + //tree to its parent in the topological ordering. + ElementTree[] treeArray = new ElementTree[trees.size()]; + trees.toArray(treeArray); + ElementTree[] sorted = sortTrees(treeArray); + // if there was a problem sorting the tree, bail on trying to collapse. + // We will be able to GC the layers at a later time. + if (sorted == null) + return; + for (int i = 1; i < sorted.length; i++) + sorted[i].collapseTo(sorted[i - 1]); + } + + protected void commit(Map contexts) throws CoreException { + for (Iterator i = contexts.values().iterator(); i.hasNext();) + i.next().commit(); + } + + /** + * Given a collection of save participants, compute the collection of + * SaveContexts to use during the save lifecycle. + * The keys are plugins and values are SaveContext objects. + */ + protected Map computeSaveContexts(String[] pluginIds, int kind, IProject project) { + HashMap result = new HashMap(pluginIds.length); + for (int i = 0; i < pluginIds.length; i++) { + String pluginId = pluginIds[i]; + try { + SaveContext context = new SaveContext(pluginId, kind, project); + result.put(pluginId, context); + } catch (CoreException e) { + // FIXME: should return a status to the user and not just log it + Policy.log(e.getStatus()); + } + } + return result; + } + + /** + * Returns a table mapping having the plug-in id as the key and the old tree + * as the value. + * This table is based on the union of the current savedStates + * and the given table of contexts. The specified tree is used as the tree for + * any newly created saved states. This method is used to compute the set of + * saved states to be written out. + */ + protected Map computeStatesToSave(Map contexts, ElementTree current) { + HashMap result = new HashMap(savedStates.size() * 2); + synchronized (savedStates) { + for (Iterator i = savedStates.values().iterator(); i.hasNext();) { + SavedState state = i.next(); + if (state.oldTree != null) + result.put(state.pluginId, state.oldTree); + } + } + for (Iterator i = contexts.values().iterator(); i.hasNext();) { + SaveContext context = i.next(); + if (!context.isDeltaNeeded()) + continue; + String pluginId = context.getPluginId(); + result.put(pluginId, current); + } + return result; + } + + protected void executeLifecycle(int lifecycle, ISaveParticipant participant, SaveContext context) throws CoreException { + switch (lifecycle) { + case PREPARE_TO_SAVE : + participant.prepareToSave(context); + break; + case SAVING : + try { + if (ResourceStats.TRACE_SAVE_PARTICIPANTS) + ResourceStats.startSave(participant); + participant.saving(context); + } finally { + if (ResourceStats.TRACE_SAVE_PARTICIPANTS) + ResourceStats.endSave(); + } + break; + case DONE_SAVING : + participant.doneSaving(context); + break; + case ROLLBACK : + participant.rollback(context); + break; + default : + Assert.isTrue(false, "Invalid save lifecycle code"); //$NON-NLS-1$ + } + } + + public void forgetSavedTree(String pluginId) { + if (pluginId == null) { + synchronized (savedStates) { + for (Iterator i = savedStates.values().iterator(); i.hasNext();) + i.next().forgetTrees(); + } + } else { + SavedState state = savedStates.get(pluginId); + if (state != null) + state.forgetTrees(); + } + } + + /** + * Used in the policy for cleaning up tree's of plug-ins that are not often activated. + */ + protected long getDeltaExpiration(String pluginId) { + String result = masterTable.getProperty(DELTA_EXPIRATION_PREFIX + pluginId); + return (result == null) ? System.currentTimeMillis() : new Long(result).longValue(); + } + + protected Properties getMasterTable() { + return masterTable; + } + + public int getSaveNumber(String pluginId) { + String value = masterTable.getProperty(SAVE_NUMBER_PREFIX + pluginId); + return (value == null) ? 0 : new Integer(value).intValue(); + } + + protected String[] getSaveParticipantPluginIds() { + synchronized (saveParticipants) { + return saveParticipants.keySet().toArray(new String[saveParticipants.size()]); + } + } + + /** + * Hooks the end of a save operation, for debugging and performance + * monitoring purposes. + */ + private void hookEndSave(int kind, IProject project, long start) { + if (ResourceStats.TRACE_SNAPSHOT && kind == ISaveContext.SNAPSHOT) + ResourceStats.endSnapshot(); + if (Policy.DEBUG_SAVE) { + String endMessage = null; + switch (kind) { + case ISaveContext.FULL_SAVE : + endMessage = DEBUG_FULL_SAVE; + break; + case ISaveContext.SNAPSHOT : + endMessage = DEBUG_SNAPSHOT; + break; + case ISaveContext.PROJECT_SAVE : + endMessage = DEBUG_PROJECT_SAVE + project.getFullPath() + ": "; //$NON-NLS-1$ + break; + } + if (endMessage != null) + Policy.debug(endMessage + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ + } + } + + /** + * Hooks the start of a save operation, for debugging and performance + * monitoring purposes. + */ + private void hookStartSave(int kind, Project project) { + if (ResourceStats.TRACE_SNAPSHOT && kind == ISaveContext.SNAPSHOT) + ResourceStats.startSnapshot(); + if (Policy.DEBUG_SAVE) { + switch (kind) { + case ISaveContext.FULL_SAVE : + Policy.debug(DEBUG_FULL_SAVE + DEBUG_START); + break; + case ISaveContext.SNAPSHOT : + Policy.debug(DEBUG_SNAPSHOT + DEBUG_START); + break; + case ISaveContext.PROJECT_SAVE : + Policy.debug(DEBUG_PROJECT_SAVE + project.getFullPath() + DEBUG_START); + break; + } + } + } + + /** + * Initializes the snapshot mechanism for this workspace. + */ + protected void initSnap(IProgressMonitor monitor) { + // Discard any pending snapshot request. + snapshotJob.cancel(); + // The "lastSnap" tree must be frozen as the exact tree obtained from startup, + // otherwise ensuing snapshot deltas may be based on an incorrect tree (see bug 12575). + lastSnap = workspace.getElementTree(); + lastSnap.immutable(); + workspace.newWorkingTree(); + operationCount = 0; + // Delete the snapshot files, if any. + IPath location = workspace.getMetaArea().getSnapshotLocationFor(workspace.getRoot()); + java.io.File target = location.toFile().getParentFile(); + FilenameFilter filter = new FilenameFilter() { + @Override + public boolean accept(java.io.File dir, String name) { + if (!name.endsWith(LocalMetaArea.F_SNAP)) + return false; + for (int i = 0; i < name.length() - LocalMetaArea.F_SNAP.length(); i++) { + char c = name.charAt(i); + if (c < '0' || c > '9') + return false; + } + return true; + } + }; + String[] candidates = target.list(filter); + if (candidates != null) + removeFiles(target, candidates, Collections. emptyList()); + } + + protected boolean isDeltaCleared(String pluginId) { + String clearDelta = masterTable.getProperty(CLEAR_DELTA_PREFIX + pluginId); + return clearDelta != null && clearDelta.equals("true"); //$NON-NLS-1$ + } + + protected boolean isOldPluginTree(String pluginId) { + // first, check if this plug-ins was marked not to receive a delta + if (isDeltaCleared(pluginId)) + return false; + //see if the plugin is still installed + if (Platform.getBundle(pluginId) == null) + return true; + + //finally see if the delta has past its expiry date + long deltaAge = System.currentTimeMillis() - getDeltaExpiration(pluginId); + return deltaAge > workspace.internalGetDescription().getDeltaExpiration(); + } + + /** + * @see IElementInfoFlattener#readElement(IPath, DataInput) + */ + @Override + public Object readElement(IPath path, DataInput input) throws IOException { + Assert.isNotNull(path); + Assert.isNotNull(input); + // read the flags and pull out the type. + int flags = input.readInt(); + int type = (flags & ICoreConstants.M_TYPE) >> ICoreConstants.M_TYPE_START; + ResourceInfo info = workspace.newElement(type); + info.readFrom(flags, input); + return info; + } + + private void rememberSnapshotRequestor() { + if (Policy.DEBUG_SAVE) + Policy.debug(new RuntimeException("Scheduling workspace snapshot")); //$NON-NLS-1$ + if (snapshotRequestor == null) { + String msg = "The workspace will exit with unsaved changes in this session."; //$NON-NLS-1$ + snapshotRequestor = new ResourceStatus(ICoreConstants.CRASH_DETECTED, msg); + } + } + + /** + * Remove marks from current save participants. This marks prevent them to receive their + * deltas when they register themselves as save participants. + */ + protected void removeClearDeltaMarks() { + synchronized (saveParticipants) { + for (Iterator i = saveParticipants.keySet().iterator(); i.hasNext();) { + String pluginId = i.next(); + removeClearDeltaMarks(pluginId); + } + } + } + + protected void removeClearDeltaMarks(String pluginId) { + masterTable.setProperty(CLEAR_DELTA_PREFIX + pluginId, "false"); //$NON-NLS-1$ + } + + protected void removeFiles(java.io.File root, String[] candidates, List exclude) { + for (int i = 0; i < candidates.length; i++) { + boolean delete = true; + for (ListIterator it = exclude.listIterator(); it.hasNext();) { + String s = it.next(); + if (s.equals(candidates[i])) { + it.remove(); + delete = false; + break; + } + } + if (delete) + new java.io.File(root, candidates[i]).delete(); + } + } + + private void removeGarbage(DataOutputStream output, IPath location, IPath tempLocation) throws IOException { + if (output.size() == 0) { + output.close(); + location.toFile().delete(); + tempLocation.toFile().delete(); + } + } + + public void removeParticipant(String pluginId) { + saveParticipants.remove(pluginId); + } + + protected void removeUnusedSafeTables() { + List valuables = new ArrayList(10); + IPath location = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES); + valuables.add(location.lastSegment()); // add master table + for (Enumeration e = masterTable.keys(); e.hasMoreElements();) { + String key = (String) e.nextElement(); + if (key.startsWith(SAVE_NUMBER_PREFIX)) { + String pluginId = key.substring(SAVE_NUMBER_PREFIX.length()); + valuables.add(workspace.getMetaArea().getSafeTableLocationFor(pluginId).lastSegment()); + } + } + java.io.File target = location.toFile().getParentFile(); + String[] candidates = target.list(); + if (candidates == null) + return; + removeFiles(target, candidates, valuables); + } + + protected void removeUnusedTreeFiles() { + // root resource + List valuables = new ArrayList(10); + IPath location = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), false); + valuables.add(location.lastSegment()); + java.io.File target = location.toFile().getParentFile(); + FilenameFilter filter = new FilenameFilter() { + @Override + public boolean accept(java.io.File dir, String name) { + return name.endsWith(LocalMetaArea.F_TREE); + } + }; + String[] candidates = target.list(filter); + if (candidates != null) + removeFiles(target, candidates, valuables); + + // projects + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) { + location = workspace.getMetaArea().getTreeLocationFor(projects[i], false); + valuables.add(location.lastSegment()); + target = location.toFile().getParentFile(); + candidates = target.list(filter); + if (candidates != null) + removeFiles(target, candidates, valuables); + } + } + + protected void reportSnapshotRequestor() { + if (snapshotRequestor != null) + Policy.log(snapshotRequestor); + } + + public void requestSnapshot() { + snapshotRequested = true; + } + + /** + * Reset the snapshot mechanism for the non-workspace files. This + * includes the markers and sync info. + */ + protected void resetSnapshots(IResource resource) throws CoreException { + Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT); + String message; + + // delete the snapshot file, if any + java.io.File file = workspace.getMetaArea().getMarkersSnapshotLocationFor(resource).toFile(); + if (file.exists()) + file.delete(); + if (file.exists()) { + message = Messages.resources_resetMarkers; + throw new ResourceException(IResourceStatus.FAILED_DELETE_METADATA, resource.getFullPath(), message, null); + } + + // delete the snapshot file, if any + file = workspace.getMetaArea().getSyncInfoSnapshotLocationFor(resource).toFile(); + if (file.exists()) + file.delete(); + if (file.exists()) { + message = Messages.resources_resetSync; + throw new ResourceException(IResourceStatus.FAILED_DELETE_METADATA, resource.getFullPath(), message, null); + } + + // if we have the workspace root then recursive over the projects. + // only do open projects since closed ones are saved elsewhere + if (resource.getType() == IResource.PROJECT) + return; + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + resetSnapshots(projects[i]); + } + + /** + * Restores the state of this workspace by opening the projects + * which were open when it was last saved. + */ + protected void restore(IProgressMonitor monitor) throws CoreException { + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore workspace: starting..."); //$NON-NLS-1$ + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", 50); //$NON-NLS-1$ + // need to open the tree to restore, but since we're not + // inside an operation, be sure to close it afterwards + workspace.newWorkingTree(); + try { + String msg = Messages.resources_startupProblems; + MultiStatus problems = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, msg, null); + + restoreMasterTable(); + // restore the saved tree and overlay the snapshots if any + restoreTree(Policy.subMonitorFor(monitor, 10)); + restoreSnapshots(Policy.subMonitorFor(monitor, 10)); + + // tolerate failure for non-critical information + // if startup fails, the entire workspace is shot + try { + restoreMarkers(workspace.getRoot(), false, Policy.subMonitorFor(monitor, 10)); + } catch (CoreException e) { + problems.merge(e.getStatus()); + } + try { + restoreSyncInfo(workspace.getRoot(), Policy.subMonitorFor(monitor, 10)); + } catch (CoreException e) { + problems.merge(e.getStatus()); + } + // restore meta info last because it might close a project if its description is not readable + restoreMetaInfo(problems, Policy.subMonitorFor(monitor, 10)); + IProject[] roots = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < roots.length; i++) + ((Project) roots[i]).startup(); + if (!problems.isOK()) + Policy.log(problems); + } finally { + workspace.getElementTree().immutable(); + } + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Restores the contents of this project. Throw + * an exception if the project could not be restored. + * @return true if the project data was restored successfully, + * and false if non-critical problems occurred while restoring. + * @exception CoreException if the project could not be restored. + */ + protected boolean restore(Project project, IProgressMonitor monitor) throws CoreException { + boolean status = true; + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": starting..."); //$NON-NLS-1$ //$NON-NLS-2$ + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", 40); //$NON-NLS-1$ + if (project.isOpen()) { + status = restoreTree(project, Policy.subMonitorFor(monitor, 10)); + } else { + monitor.worked(10); + } + restoreMarkers(project, true, Policy.subMonitorFor(monitor, 10)); + restoreSyncInfo(project, Policy.subMonitorFor(monitor, 10)); + // restore meta info last because it might close a project if its description is not found + restoreMetaInfo(project, Policy.subMonitorFor(monitor, 10)); + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return status; + } + + /** + * Restores the contents of this project from a refresh snapshot, if possible. + * Throws an exception if the snapshot is found but an error occurs when reading + * the file. + * @return true if the project data was restored successfully, + * and false if the refresh snapshot was not found or could not be opened. + * @exception CoreException if an error occurred reading the snapshot file. + */ + protected boolean restoreFromRefreshSnapshot(Project project, IProgressMonitor monitor) throws CoreException { + boolean status = true; + IPath snapshotPath = workspace.getMetaArea().getRefreshLocationFor(project); + java.io.File snapshotFile = snapshotPath.toFile(); + if (!snapshotFile.exists()) + return false; + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": starting..."); //$NON-NLS-1$ //$NON-NLS-2$ + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", 40); //$NON-NLS-1$ + status = restoreTreeFromRefreshSnapshot(project, snapshotFile, Policy.subMonitorFor(monitor, 40)); + if (status) { + // load the project description and set internal description + ProjectDescription description = workspace.getFileSystemManager().read(project, true); + project.internalSetDescription(description, false); + workspace.getMetaArea().clearRefresh(project); + } + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return status; + } + + /** + * Reads the markers which were originally saved + * for the tree rooted by the given resource. + */ + protected void restoreMarkers(IResource resource, boolean generateDeltas, IProgressMonitor monitor) throws CoreException { + Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT); + long start = System.currentTimeMillis(); + MarkerManager markerManager = workspace.getMarkerManager(); + // when restoring a project, only load markers if it is open + if (resource.isAccessible()) + markerManager.restore(resource, generateDeltas, monitor); + + // if we have the workspace root then restore markers for its projects + if (resource.getType() == IResource.PROJECT) { + if (Policy.DEBUG_RESTORE_MARKERS) { + Policy.debug("Restore Markers for " + resource.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return; + } + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + if (projects[i].isAccessible()) + markerManager.restore(projects[i], generateDeltas, monitor); + if (Policy.DEBUG_RESTORE_MARKERS) { + Policy.debug("Restore Markers for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + protected void restoreMasterTable() throws CoreException { + long start = System.currentTimeMillis(); + masterTable.clear(); + IPath location = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES); + java.io.File target = location.toFile(); + if (!target.exists()) { + location = workspace.getMetaArea().getBackupLocationFor(location); + target = location.toFile(); + if (!target.exists()) + return; + } + try { + SafeChunkyInputStream input = new SafeChunkyInputStream(target); + try { + masterTable.load(input); + } finally { + input.close(); + } + } catch (IOException e) { + String message = Messages.resources_exMasterTable; + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); + } + if (Policy.DEBUG_RESTORE_MASTERTABLE) + Policy.debug("Restore master table for " + location + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Restores the state of this workspace by opening the projects + * which were open when it was last saved. + */ + protected void restoreMetaInfo(MultiStatus problems, IProgressMonitor monitor) { + if (Policy.DEBUG_RESTORE_METAINFO) + Policy.debug("Restore workspace metainfo: starting..."); //$NON-NLS-1$ + long start = System.currentTimeMillis(); + IProject[] roots = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < roots.length; i++) { + //fatal to throw exceptions during startup + try { + restoreMetaInfo((Project) roots[i], monitor); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_readMeta, roots[i].getName()); + problems.merge(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, roots[i].getFullPath(), message, e)); + } + } + if (Policy.DEBUG_RESTORE_METAINFO) + Policy.debug("Restore workspace metainfo: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Restores the contents of this project. Throw an exception if the + * project description could not be restored. + */ + protected void restoreMetaInfo(Project project, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + ProjectDescription description = null; + CoreException failure = null; + try { + if (project.isOpen()) + description = workspace.getFileSystemManager().read(project, true); + else + //for closed projects, just try to read the legacy .prj file, + //because the project location is stored there. + description = workspace.getMetaArea().readOldDescription(project); + } catch (CoreException e) { + failure = e; + } + // If we had an open project and there was an error reading the description + // from disk, close the project and give it a default description. If the project + // was already closed then just set a default description. + if (description == null) { + description = new ProjectDescription(); + description.setName(project.getName()); + //try to read private metadata and add to the description + workspace.getMetaArea().readPrivateDescription(project, description); + } + project.internalSetDescription(description, false); + if (failure != null) { + // write the project tree ... + writeTree(project, IResource.DEPTH_INFINITE); + // ... and close the project + project.internalClose(); + throw failure; + } + if (Policy.DEBUG_RESTORE_METAINFO) + Policy.debug("Restore metainfo for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Restores the workspace tree from snapshot files in the event + * of a crash. The workspace tree must be open when this method + * is called, and will be open at the end of this method. In the + * event of a crash recovery, the snapshot file is not deleted until + * the next successful save. + */ + protected void restoreSnapshots(IProgressMonitor monitor) { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + IPath snapLocation = workspace.getMetaArea().getSnapshotLocationFor(workspace.getRoot()); + java.io.File localFile = snapLocation.toFile(); + + if (!localFile.exists()) { + // The snapshot corresponding to the current tree version doesn't exist. + // Try the legacy non-versioned snapshot, but ignore it if it is older than + // the tree. + snapLocation = workspace.getMetaArea().getLegacySnapshotLocationFor(workspace.getRoot()); + localFile = snapLocation.toFile(); + if (!localFile.exists() || isSnapshotOlderThanTree(localFile)) { + // If the snapshot file doesn't exist, there was no crash. + // Just initialize the snapshot file and return. + initSnap(Policy.subMonitorFor(monitor, Policy.totalWork / 2)); + return; + } + } + // If we have a snapshot file, the workspace was shutdown without being saved or crashed. + workspace.setCrashed(true); + try { + /* Read each of the snapshots and lay them on top of the current tree.*/ + ElementTree complete = workspace.getElementTree(); + complete.immutable(); + DataInputStream input = new DataInputStream(new SafeChunkyInputStream(localFile)); + try { + WorkspaceTreeReader reader = WorkspaceTreeReader.getReader(workspace, input.readInt()); + complete = reader.readSnapshotTree(input, complete, monitor); + } finally { + FileUtil.safeClose(input); + //reader returned an immutable tree, but since we're inside + //an operation, we must return an open tree + lastSnap = complete; + complete = complete.newEmptyDelta(); + workspace.tree = complete; + } + } catch (Exception e) { + // only log the exception, we should not fail restoring the snapshot + message = Messages.resources_snapRead; + Policy.log(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, null, message, e)); + } + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE_SNAPSHOTS) + Policy.debug("Restore snapshots for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Checks if the given snapshot file is older than the tree file. + * + * @param snapshot the snapshot file to check + * @return {@code true} if the snapshot file is older than the tree file or the tree file + * does not exist + */ + private boolean isSnapshotOlderThanTree(File snapshot) { + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), false); + File tree = treeLocation.toFile(); + if (!tree.exists()) { + treeLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + tree = treeLocation.toFile(); + if (!tree.exists()) + return false; + } + return snapshot.lastModified() < tree.lastModified(); + } + + /** + * Reads the sync info which was originally saved + * for the tree rooted by the given resource. + */ + protected void restoreSyncInfo(IResource resource, IProgressMonitor monitor) throws CoreException { + Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT); + long start = System.currentTimeMillis(); + Synchronizer synchronizer = (Synchronizer) workspace.getSynchronizer(); + // when restoring a project, only load sync info if it is open + if (resource.isAccessible()) + synchronizer.restore(resource, monitor); + + // restore sync info for all projects if we were given the workspace root. + if (resource.getType() == IResource.PROJECT) { + if (Policy.DEBUG_RESTORE_SYNCINFO) { + Policy.debug("Restore SyncInfo for " + resource.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return; + } + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + if (projects[i].isAccessible()) + synchronizer.restore(projects[i], monitor); + if (Policy.DEBUG_RESTORE_SYNCINFO) { + Policy.debug("Restore SyncInfo for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Reads the contents of the tree rooted by the given resource from the + * file system. This method is used when restoring a complete workspace + * after workspace save/shutdown. + * @exception CoreException if the workspace could not be restored. + */ + protected void restoreTree(IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), false); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + if (!treeLocation.toFile().exists() && !tempLocation.toFile().exists()) { + savedStates = Collections.synchronizedMap(new HashMap(10)); + return; + } + try { + DataInputStream input = new DataInputStream(new SafeFileInputStream(treeLocation.toOSString(), tempLocation.toOSString(), TREE_BUFFER_SIZE)); + try { + WorkspaceTreeReader.getReader(workspace, input.readInt()).readTree(input, monitor); + } finally { + input.close(); + } + } catch (IOException e) { + String msg = NLS.bind(Messages.resources_readMeta, treeLocation.toOSString()); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, treeLocation, msg, e); + } + if (Policy.DEBUG_RESTORE_TREE) { + Policy.debug("Restore Tree for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Restores the trees for the builders of this project from the local disk. + * Does nothing if the tree file does not exist (this means the + * project has never been saved). This method is + * used when restoring a saved/closed project. restoreTree(Workspace) is + * used when restoring a complete workspace after workspace save/shutdown. + * @return true if the tree file exists, false otherwise. + * @exception CoreException if the project could not be restored. + */ + protected boolean restoreTree(Project project, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(project, false); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + if (!treeLocation.toFile().exists() && !tempLocation.toFile().exists()) + return false; + DataInputStream input = new DataInputStream(new SafeFileInputStream(treeLocation.toOSString(), tempLocation.toOSString())); + try { + WorkspaceTreeReader reader = WorkspaceTreeReader.getReader(workspace, input.readInt()); + reader.readTree(project, input, Policy.subMonitorFor(monitor, Policy.totalWork)); + } finally { + input.close(); + } + } catch (IOException e) { + message = NLS.bind(Messages.resources_readMeta, project.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, project.getFullPath(), message, e); + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE_TREE) { + Policy.debug("Restore Tree for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return true; + } + + /** + * Restores a tree saved as a refresh snapshot to a specified URI. + * @return true if the snapshot exists, false otherwise. + * @exception CoreException if the project could not be restored. + */ + protected boolean restoreTreeFromRefreshSnapshot(Project project, java.io.File snapshotFile, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + IPath snapshotPath = null; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + InputStream snapIn = new FileInputStream(snapshotFile); + ZipInputStream zip = new ZipInputStream(snapIn); + ZipEntry treeEntry = zip.getNextEntry(); + if (treeEntry == null || !treeEntry.getName().equals("resource-index.tree")) { //$NON-NLS-1$ + zip.close(); + return false; + } + DataInputStream input = new DataInputStream(zip); + try { + WorkspaceTreeReader reader = WorkspaceTreeReader.getReader(workspace, input.readInt(), true); + reader.readTree(project, input, Policy.subMonitorFor(monitor, Policy.totalWork)); + } finally { + input.close(); + zip.close(); + } + } catch (IOException e) { + snapshotPath = new Path(snapshotFile.getPath()); + message = NLS.bind(Messages.resources_readMeta, snapshotPath); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, snapshotPath, message, e); + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE_TREE) { + Policy.debug("Restore Tree for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return true; + } + + class InternalMonitorWrapper extends ProgressMonitorWrapper { + private boolean ignoreCancel; + + public InternalMonitorWrapper(IProgressMonitor monitor) { + super(Policy.monitorFor(monitor)); + } + + public void ignoreCancelState(boolean ignore) { + this.ignoreCancel = ignore; + } + + @Override + public boolean isCanceled() { + return ignoreCancel ? false : super.isCanceled(); + } + } + + public IStatus save(int kind, Project project, IProgressMonitor monitor) throws CoreException { + return save(kind, false, project, monitor); + } + + public IStatus save(int kind, boolean keepConsistencyWhenCanceled, Project project, IProgressMonitor parentMonitor) throws CoreException { + InternalMonitorWrapper monitor = new InternalMonitorWrapper(parentMonitor); + monitor.ignoreCancelState(keepConsistencyWhenCanceled); + try { + isSaving = true; + String message = Messages.resources_saving_0; + monitor.beginTask(message, 7); + message = Messages.resources_saveWarnings; + MultiStatus warnings = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.WARNING, message, null); + ISchedulingRule rule = project != null ? (IResource) project : workspace.getRoot(); + try { + workspace.prepareOperation(rule, monitor); + workspace.beginOperation(false); + hookStartSave(kind, project); + long start = System.currentTimeMillis(); + Map contexts = computeSaveContexts(getSaveParticipantPluginIds(), kind, project); + broadcastLifecycle(PREPARE_TO_SAVE, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + try { + broadcastLifecycle(SAVING, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + switch (kind) { + case ISaveContext.FULL_SAVE : + // save the complete tree and remember all of the required saved states + saveTree(contexts, Policy.subMonitorFor(monitor, 1)); + // reset the snapshot state. + initSnap(null); + snapshotRequestor = null; + //save master table right after saving tree to ensure correct tree number is saved + cleanMasterTable(); + // save all of the markers and all sync info in the workspace + persistMarkers = 0l; + persistSyncInfo = 0l; + visitAndSave(workspace.getRoot()); + monitor.worked(1); + if (Policy.DEBUG_SAVE) { + Policy.debug("Total Save Markers: " + persistMarkers + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + Policy.debug("Total Save Sync Info: " + persistSyncInfo + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + // reset the snap shot files + resetSnapshots(workspace.getRoot()); + //remove unused files + removeUnusedSafeTables(); + removeUnusedTreeFiles(); + + // history pruning can be always canceled + monitor.ignoreCancelState(false); + workspace.getFileSystemManager().getHistoryStore().clean(Policy.subMonitorFor(monitor, 1)); + monitor.ignoreCancelState(keepConsistencyWhenCanceled); + + // write out all metainfo (e.g., workspace/project descriptions) + saveMetaInfo(warnings, Policy.subMonitorFor(monitor, 1)); + break; + case ISaveContext.SNAPSHOT : + snapTree(workspace.getElementTree(), Policy.subMonitorFor(monitor, 1)); + // snapshot the markers and sync info for the workspace + persistMarkers = 0l; + persistSyncInfo = 0l; + visitAndSnap(workspace.getRoot()); + monitor.worked(1); + if (Policy.DEBUG_SAVE) { + Policy.debug("Total Snap Markers: " + persistMarkers + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + Policy.debug("Total Snap Sync Info: " + persistSyncInfo + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + collapseTrees(contexts); + clearSavedDelta(); + // write out all metainfo (e.g., workspace/project descriptions) + saveMetaInfo(warnings, Policy.subMonitorFor(monitor, 1)); + break; + case ISaveContext.PROJECT_SAVE : + writeTree(project, IResource.DEPTH_INFINITE); + monitor.worked(1); + // save markers and sync info + visitAndSave(project); + monitor.worked(1); + // reset the snapshot file + resetSnapshots(project); + IStatus result = saveMetaInfo(project, null); + if (!result.isOK()) + warnings.merge(result); + monitor.worked(1); + break; + } + // save contexts + commit(contexts); + if (kind == ISaveContext.FULL_SAVE) + removeClearDeltaMarks(); + //this must be done after committing save contexts to update participant save numbers + saveMasterTable(kind); + broadcastLifecycle(DONE_SAVING, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + hookEndSave(kind, project, start); + return warnings; + } catch (CoreException e) { + broadcastLifecycle(ROLLBACK, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + // rollback ResourcesPlugin master table + restoreMasterTable(); + throw e; // re-throw + } + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, false, Policy.monitorFor(null)); + } + } finally { + isSaving = false; + monitor.done(); + } + } + + protected void saveMasterTable(int kind) throws CoreException { + saveMasterTable(kind, workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES)); + } + + protected void saveMasterTable(int kind, IPath location) throws CoreException { + long start = System.currentTimeMillis(); + java.io.File target = location.toFile(); + try { + if (kind == ISaveContext.FULL_SAVE || kind == ISaveContext.SNAPSHOT) + validateMasterTableBeforeSave(target); + SafeChunkyOutputStream output = new SafeChunkyOutputStream(target); + try { + masterTable.store(output, "master table"); //$NON-NLS-1$ + output.succeed(); + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (IOException e) { + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, NLS.bind(Messages.resources_exSaveMaster, location.toOSString()), e); + } + if (Policy.DEBUG_SAVE_MASTERTABLE) + Policy.debug("Save master table for " + location + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Writes the metainfo (e.g. descriptions) of the given workspace and + * all projects to the local disk. + */ + protected void saveMetaInfo(MultiStatus problems, IProgressMonitor monitor) throws CoreException { + if (Policy.DEBUG_SAVE_METAINFO) + Policy.debug("Save workspace metainfo: starting..."); //$NON-NLS-1$ + long start = System.currentTimeMillis(); + // save preferences (workspace description, path variables, etc) + ResourcesPlugin.getPlugin().savePluginPreferences(); + // save projects' meta info + IProject[] roots = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < roots.length; i++) + if (roots[i].isAccessible()) { + IStatus result = saveMetaInfo((Project) roots[i], null); + if (!result.isOK()) + problems.merge(result); + } + if (Policy.DEBUG_SAVE_METAINFO) + Policy.debug("Save workspace metainfo: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Ensures that the project meta-info is saved. The project meta-info + * is usually saved as soon as it changes, so this is just a sanity check + * to make sure there is something on disk before we shutdown. + * + * @return Status object containing non-critical warnings, or an OK status. + */ + protected IStatus saveMetaInfo(Project project, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + //if there is nothing on disk, write the description + if (!workspace.getFileSystemManager().hasSavedDescription(project)) { + workspace.getFileSystemManager().writeSilently(project); + String msg = NLS.bind(Messages.resources_missingProjectMetaRepaired, project.getName()); + return new ResourceStatus(IResourceStatus.MISSING_DESCRIPTION_REPAIRED, project.getFullPath(), msg); + } + if (Policy.DEBUG_SAVE_METAINFO) + Policy.debug("Save metainfo for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return Status.OK_STATUS; + } + + /** + * Writes a snapshot of project refresh information to the specified + * location. + * @param project the project to write a refresh snapshot for + * @param monitor progress monitor + * @exception CoreException if there is a problem writing the snapshot. + */ + public void saveRefreshSnapshot(Project project, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + IFileStore store = EFS.getStore(snapshotLocation); + IPath snapshotPath = new Path(snapshotLocation.getPath()); + java.io.File tmpTree = null; + try { + tmpTree = java.io.File.createTempFile("tmp", ".tree"); //$NON-NLS-1$//$NON-NLS-2$ + } catch (IOException e) { + throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, snapshotPath, Messages.resources_copyProblem, e); + } + ZipOutputStream out = null; + try { + FileOutputStream fis = new FileOutputStream(tmpTree); + DataOutputStream output = new DataOutputStream(fis); + try { + output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeTree(project, output, monitor); + output.close(); + } finally { + FileUtil.safeClose(output); + } + OutputStream snapOut = store.openOutputStream(EFS.NONE, monitor); + out = new ZipOutputStream(snapOut); + out.setLevel(Deflater.BEST_COMPRESSION); + ZipEntry e = new ZipEntry("resource-index.tree"); //$NON-NLS-1$ + out.putNextEntry(e); + int read = 0; + byte[] buffer = new byte[4096]; + InputStream in = new FileInputStream(tmpTree); + try { + while ((read = in.read(buffer)) >= 0) { + out.write(buffer, 0, read); + } + out.closeEntry(); + in.close(); + } finally { + FileUtil.safeClose(in); + } + out.close(); + } catch (IOException e) { + throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, snapshotPath, Messages.resources_copyProblem, e); + } finally { + FileUtil.safeClose(out); + if (tmpTree != null) + tmpTree.delete(); + } + } + + /** + * Writes the current state of the entire workspace tree to disk. + * This is used during workspace save. saveTree(Project) + * is used to save the state of an individual project. + * @exception CoreException if there is a problem writing the tree to disk. + */ + protected void saveTree(Map contexts, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), true); + try { + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + DataOutputStream output = new DataOutputStream(new SafeFileOutputStream(treeLocation.toOSString(), tempLocation.toOSString())); + try { + output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeTree(computeStatesToSave(contexts, workspace.getElementTree()), output, monitor); + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (Exception e) { + String msg = NLS.bind(Messages.resources_writeWorkspaceMeta, treeLocation); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, Path.ROOT, msg, e); + } + if (Policy.DEBUG_SAVE_TREE) + Policy.debug("Save Workspace Tree: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Should only be used for read purposes. + */ + void setPluginsSavedState(HashMap savedStates) { + this.savedStates = Collections.synchronizedMap(savedStates); + } + + protected void setSaveNumber(String pluginId, int number) { + masterTable.setProperty(SAVE_NUMBER_PREFIX + pluginId, new Integer(number).toString()); + } + + /* (non-Javadoc) + * Method declared on IStringPoolParticipant + */ + @Override + public void shareStrings(StringPool pool) { + lastSnap.shareStrings(pool); + } + + @Override + public void shutdown(final IProgressMonitor monitor) { + // do a last snapshot if it was scheduled + // we force it in the same thread because it would not + // help if the job runs after we close the workspace + int state = snapshotJob.getState(); + if (state == Job.WAITING || state == Job.SLEEPING) + // we cannot pass null to Job#run + snapshotJob.run(Policy.monitorFor(monitor)); + // cancel the snapshot job + snapshotJob.cancel(); + } + + /** + * Performs a snapshot if one is deemed necessary. + * Encapsulates rules for determining when a snapshot is needed. + * This should be called at the end of every top level operation. + */ + public void snapshotIfNeeded(boolean hasTreeChanges) { + // never schedule a snapshot while save is occurring. + if (isSaving) + return; + if (snapshotRequested || operationCount >= workspace.internalGetDescription().getOperationsPerSnapshot()) { + rememberSnapshotRequestor(); + if (snapshotJob.getState() == Job.NONE) + snapshotJob.schedule(); + else + snapshotJob.wakeUp(); + } else { + if (hasTreeChanges) { + operationCount++; + if (snapshotJob.getState() == Job.NONE) { + rememberSnapshotRequestor(); + long interval = workspace.internalGetDescription().getSnapshotInterval(); + snapshotJob.schedule(Math.max(interval, MIN_SNAPSHOT_DELAY)); + } + } else { + //only increment the operation count if we've had a sufficient number of no-ops + if (++noopCount > NO_OP_THRESHOLD) { + operationCount++; + noopCount = 0; + } + } + } + } + + /** + * Performs a snapshot of the workspace tree. + */ + protected void snapTree(ElementTree tree, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + //the tree must be immutable + tree.immutable(); + // don't need to snapshot if there are no changes + if (tree == lastSnap) + return; + operationCount = 0; + IPath snapPath = workspace.getMetaArea().getSnapshotLocationFor(workspace.getRoot()); + ElementTreeWriter writer = new ElementTreeWriter(this); + java.io.File localFile = snapPath.toFile(); + try { + SafeChunkyOutputStream safeStream = new SafeChunkyOutputStream(localFile); + DataOutputStream out = new DataOutputStream(safeStream); + try { + out.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeWorkspaceFields(out, monitor); + writer.writeDelta(tree, lastSnap, Path.ROOT, ElementTreeWriter.D_INFINITE, out, ResourceComparator.getSaveComparator()); + safeStream.succeed(); + out.close(); + } finally { + FileUtil.safeClose(out); + } + } catch (IOException e) { + message = NLS.bind(Messages.resources_writeWorkspaceMeta, localFile.getAbsolutePath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, Path.ROOT, message, e); + } + lastSnap = tree; + } finally { + monitor.done(); + } + if (Policy.DEBUG_SAVE_TREE) + Policy.debug("Snapshot Workspace Tree: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Sorts the given array of trees so that the following rules are true: + * - The first tree has no parent + * - No tree has an ancestor with a greater index in the array. + * If there are no missing parents in the given trees array, this means + * that in the resulting array, the i'th tree's parent will be tree i-1. + * The input tree array may contain duplicate trees. + */ + protected ElementTree[] sortTrees(ElementTree[] trees) { + /* the sorted list */ + int numTrees = trees.length; + ElementTree[] sorted = new ElementTree[numTrees]; + + /* first build a table of ElementTree -> List of Integers(indices in trees array) */ + Map> table = new HashMap>(numTrees * 2 + 1); + for (int i = 0; i < trees.length; i++) { + List indices = table.get(trees[i]); + if (indices == null) { + indices = new ArrayList(10); + table.put(trees[i], indices); + } + indices.add(new Integer(i)); + } + + /* find the oldest tree (a descendent of all other trees) */ + ElementTree oldest = trees[ElementTree.findOldest(trees)]; + + /** + * Walk through the chain of trees from oldest to newest, + * adding them to the sorted list as we go. + */ + int i = numTrees - 1; + while (i >= 0) { + /* add all instances of the current oldest tree to the sorted list */ + List indices = table.remove(oldest); + for (Enumeration e = Collections.enumeration(indices); e.hasMoreElements();) { + e.nextElement(); + sorted[i] = oldest; + i--; + } + if (i >= 0) { + /* find the next tree in the list */ + ElementTree parent = oldest.getParent(); + while (parent != null && table.get(parent) == null) { + parent = parent.getParent(); + } + if (parent == null) { + IStatus status = new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, "null parent found while collapsing trees", null); //$NON-NLS-1$ + Policy.log(status); + return null; + } + oldest = parent; + } + } + return sorted; + } + + @Override + public void startup(IProgressMonitor monitor) throws CoreException { + restore(monitor); + java.io.File table = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES).toFile(); + if (!table.exists()) + table.getParentFile().mkdirs(); + } + + /** + * Update the expiration time for the given plug-in's tree. If the tree was never + * loaded, use the current value in the master table. If the tree has been loaded, + * use the provided new timestamp. + * + * The timestamp is used in the policy for cleaning up tree's of plug-ins that are + * not often activated. + */ + protected void updateDeltaExpiration(String pluginId) { + String key = DELTA_EXPIRATION_PREFIX + pluginId; + if (!masterTable.containsKey(key)) + masterTable.setProperty(key, Long.toString(System.currentTimeMillis())); + } + + private void validateMasterTableBeforeSave(java.io.File target) throws IOException { + if (target.exists()) { + MasterTable previousMasterTable = new MasterTable(); + SafeChunkyInputStream input = new SafeChunkyInputStream(target); + try { + previousMasterTable.load(input); + String stringValue = previousMasterTable.getProperty(ROOT_SEQUENCE_NUMBER_KEY); + // if there was a full save, then there must be a non-null entry for root + if (stringValue != null) { + int valueInFile = new Integer(stringValue).intValue(); + int valueInMemory = new Integer(masterTable.getProperty(ROOT_SEQUENCE_NUMBER_KEY)).intValue(); + // new master table must provide greater or equal sequence number for root + // throw exception if new value is lower than previous one - we cannot allow to desynchronize master table on disk + String message = "Cannot set lower sequence number for root (previous: " + valueInFile + ", new: " + valueInMemory + "). Location: " + target.getAbsolutePath(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Assert.isLegal(valueInMemory >= valueInFile, message); + } + } finally { + input.close(); + } + } + } + + /** + * Visit the given resource (to depth infinite) and write out extra information + * like markers and sync info. To be called during a full save and project save. + * + * FIXME: This method is ugly. Fix it up and look at merging with #visitAndSnap + */ + public void visitAndSave(final IResource root) throws CoreException { + // Ensure we have either a project or the workspace root + Assert.isLegal(root.getType() == IResource.ROOT || root.getType() == IResource.PROJECT); + // only write out info for accessible resources + if (!root.isAccessible()) + return; + + // Setup variables + final Synchronizer synchronizer = (Synchronizer) workspace.getSynchronizer(); + final MarkerManager markerManager = workspace.getMarkerManager(); + IPath markersLocation = workspace.getMetaArea().getMarkersLocationFor(root); + IPath markersTempLocation = workspace.getMetaArea().getBackupLocationFor(markersLocation); + IPath syncInfoLocation = workspace.getMetaArea().getSyncInfoLocationFor(root); + IPath syncInfoTempLocation = workspace.getMetaArea().getBackupLocationFor(syncInfoLocation); + final List writtenTypes = new ArrayList(5); + final List writtenPartners = new ArrayList(synchronizer.registry.size()); + DataOutputStream o1 = null; + DataOutputStream o2 = null; + String message; + + // Create the output streams + try { + o1 = new DataOutputStream(new SafeFileOutputStream(markersLocation.toOSString(), markersTempLocation.toOSString())); + // we don't store the sync info for the workspace root so don't create + // an empty file + if (root.getType() != IResource.ROOT) + o2 = new DataOutputStream(new SafeFileOutputStream(syncInfoLocation.toOSString(), syncInfoTempLocation.toOSString())); + } catch (IOException e) { + FileUtil.safeClose(o1); + FileUtil.safeClose(o2); + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } + + final DataOutputStream markersOutput = o1; + final DataOutputStream syncInfoOutput = o2; + // The following 2 piece array will hold a running total of the times + // taken to save markers and syncInfo respectively. This will cut down + // on the number of statements printed out as we would get 2 statements + // for each resource otherwise. + final long[] saveTimes = new long[2]; + + // Create the visitor + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) { + ResourceInfo info = (ResourceInfo) elementContents; + if (info != null) { + try { + // save the markers + long start = System.currentTimeMillis(); + markerManager.save(info, requestor, markersOutput, writtenTypes); + long markerSaveTime = System.currentTimeMillis() - start; + saveTimes[0] += markerSaveTime; + persistMarkers += markerSaveTime; + // save the sync info - if we have the workspace root then the output stream will be null + if (syncInfoOutput != null) { + start = System.currentTimeMillis(); + synchronizer.saveSyncInfo(info, requestor, syncInfoOutput, writtenPartners); + long syncInfoSaveTime = System.currentTimeMillis() - start; + saveTimes[1] += syncInfoSaveTime; + persistSyncInfo += syncInfoSaveTime; + } + } catch (IOException e) { + throw new WrappedRuntimeException(e); + } + } + // don't continue if the current resource is the workspace root, only continue for projects + return root.getType() != IResource.ROOT; + } + }; + + // Call the visitor + try { + try { + new ElementTreeIterator(workspace.getElementTree(), root.getFullPath()).iterate(visitor); + } catch (WrappedRuntimeException e) { + throw (IOException) e.getTargetException(); + } + if (Policy.DEBUG_SAVE_MARKERS) + Policy.debug("Save Markers for " + root.getFullPath() + ": " + saveTimes[0] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (Policy.DEBUG_SAVE_SYNCINFO) + Policy.debug("Save SyncInfo for " + root.getFullPath() + ": " + saveTimes[1] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + removeGarbage(markersOutput, markersLocation, markersTempLocation); + // if we have the workspace root the output stream will be null and we + // don't have to perform cleanup code + if (syncInfoOutput != null) { + removeGarbage(syncInfoOutput, syncInfoLocation, syncInfoTempLocation); + syncInfoOutput.close(); + } + markersOutput.close(); + } catch (IOException e) { + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } finally { + FileUtil.safeClose(markersOutput); + FileUtil.safeClose(syncInfoOutput); + } + + // recurse over the projects in the workspace if we were given the workspace root + if (root.getType() == IResource.PROJECT) + return; + IProject[] projects = ((IWorkspaceRoot) root).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + visitAndSave(projects[i]); + } + + /** + * Visit the given resource (to depth infinite) and write out extra information + * like markers and sync info. To be called during a snapshot + * + * FIXME: This method is ugly. Fix it up and look at merging with #visitAndSnap + */ + public void visitAndSnap(final IResource root) throws CoreException { + // Ensure we have either a project or the workspace root + Assert.isLegal(root.getType() == IResource.ROOT || root.getType() == IResource.PROJECT); + // only write out info for accessible resources + if (!root.isAccessible()) + return; + + // Setup variables + final Synchronizer synchronizer = (Synchronizer) workspace.getSynchronizer(); + final MarkerManager markerManager = workspace.getMarkerManager(); + IPath markersLocation = workspace.getMetaArea().getMarkersSnapshotLocationFor(root); + IPath syncInfoLocation = workspace.getMetaArea().getSyncInfoSnapshotLocationFor(root); + SafeChunkyOutputStream safeMarkerStream = null; + SafeChunkyOutputStream safeSyncInfoStream = null; + DataOutputStream o1 = null; + DataOutputStream o2 = null; + String message; + + // Create the output streams + try { + safeMarkerStream = new SafeChunkyOutputStream(markersLocation.toFile()); + o1 = new DataOutputStream(safeMarkerStream); + // we don't store the sync info for the workspace root so don't create + // an empty file + if (root.getType() != IResource.ROOT) { + safeSyncInfoStream = new SafeChunkyOutputStream(syncInfoLocation.toFile()); + o2 = new DataOutputStream(safeSyncInfoStream); + } + } catch (IOException e) { + FileUtil.safeClose(o1); + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } + + final DataOutputStream markersOutput = o1; + final DataOutputStream syncInfoOutput = o2; + int markerFileSize = markersOutput.size(); + int syncInfoFileSize = safeSyncInfoStream == null ? -1 : syncInfoOutput.size(); + // The following 2 piece array will hold a running total of the times + // taken to save markers and syncInfo respectively. This will cut down + // on the number of statements printed out as we would get 2 statements + // for each resource otherwise. + final long[] snapTimes = new long[2]; + + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) { + ResourceInfo info = (ResourceInfo) elementContents; + if (info != null) { + try { + // save the markers + long start = System.currentTimeMillis(); + markerManager.snap(info, requestor, markersOutput); + long markerSnapTime = System.currentTimeMillis() - start; + snapTimes[0] += markerSnapTime; + persistMarkers += markerSnapTime; + // save the sync info - if we have the workspace root then the output stream will be null + if (syncInfoOutput != null) { + start = System.currentTimeMillis(); + synchronizer.snapSyncInfo(info, requestor, syncInfoOutput); + long syncInfoSnapTime = System.currentTimeMillis() - start; + snapTimes[1] += syncInfoSnapTime; + persistSyncInfo += syncInfoSnapTime; + } + } catch (IOException e) { + throw new WrappedRuntimeException(e); + } + } + // don't continue if the current resource is the workspace root, only continue for projects + return root.getType() != IResource.ROOT; + } + }; + + try { + // Call the visitor + try { + new ElementTreeIterator(workspace.getElementTree(), root.getFullPath()).iterate(visitor); + } catch (WrappedRuntimeException e) { + throw (IOException) e.getTargetException(); + } + if (Policy.DEBUG_SAVE_MARKERS) + Policy.debug("Snap Markers for " + root.getFullPath() + ": " + snapTimes[0] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (Policy.DEBUG_SAVE_SYNCINFO) + Policy.debug("Snap SyncInfo for " + root.getFullPath() + ": " + snapTimes[1] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (markerFileSize != markersOutput.size()) + safeMarkerStream.succeed(); + if (safeSyncInfoStream != null && syncInfoFileSize != syncInfoOutput.size()) { + safeSyncInfoStream.succeed(); + syncInfoOutput.close(); + } + markersOutput.close(); + } catch (IOException e) { + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } finally { + FileUtil.safeClose(markersOutput); + FileUtil.safeClose(syncInfoOutput); + } + + // recurse over the projects in the workspace if we were given the workspace root + if (root.getType() == IResource.PROJECT) + return; + IProject[] projects = ((IWorkspaceRoot) root).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + visitAndSnap(projects[i]); + } + + /** + * Writes out persistent information about all builders for which a last built + * tree is available. File format is: + * int - number of builders + * for each builder: + * UTF - project name + * UTF - fully qualified builder extension name + * int - number of interesting projects for builder + * For each interesting project: + * UTF - interesting project name + */ + private void writeBuilderPersistentInfo(DataOutputStream output, List builders, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + // write the number of builders we are saving + int numBuilders = builders.size(); + output.writeInt(numBuilders); + for (int i = 0; i < numBuilders; i++) { + BuilderPersistentInfo info = builders.get(i); + output.writeUTF(info.getProjectName()); + output.writeUTF(info.getBuilderName()); + // write interesting projects + IProject[] interestingProjects = info.getInterestingProjects(); + output.writeInt(interestingProjects.length); + for (int j = 0; j < interestingProjects.length; j++) + output.writeUTF(interestingProjects[j].getName()); + } + } finally { + monitor.done(); + } + } + + @Override + public void writeElement(IPath path, Object element, DataOutput output) throws IOException { + Assert.isNotNull(path); + Assert.isNotNull(element); + Assert.isNotNull(output); + ResourceInfo info = (ResourceInfo) element; + output.writeInt(info.getFlags()); + info.writeTo(output); + } + + /** + * Discovers the trees which need to be saved for the passed in project's builders. + * In a pre-3.7 workspace, only one tree is saved per builder. + * Since 3.7 one tree may be persisted per build configuration per multi-config builder. + * + * We still provide one tree per builder first so the workspace can be opened in an older Eclipse. + * Newer eclipses will be able to load the additional per-configuration trees. + * @param project project to fetch builder trees for + * @param trees list of trees to be persisted + * @param builderInfos list of builder infos; one per builder + * @param configNames configuration names persisted for builder infos above + * @param additionalTrees remaining trees to be persisted for other configurations + * @param additionalBuilderInfos remaining builder infos for other configurations + * @param additionalConfigNames configuration names of the remaining per-configuration trees + * @throws CoreException + */ + private void getTreesToSave(IProject project, List trees, List builderInfos, List configNames, List additionalTrees, List additionalBuilderInfos, List additionalConfigNames) throws CoreException { + if (project.isOpen()) { + String activeConfigName = project.getActiveBuildConfig().getName(); + List infos = workspace.getBuildManager().createBuildersPersistentInfo(project); + if (infos != null) { + for (Iterator it = infos.iterator(); it.hasNext();) { + BuilderPersistentInfo info = it.next(); + // Nothing to persist if there isn't a previous delta tree. + // There used to be code which serialized the current workspace tree + // but this will result in the next build of the builder getting an empty delta... + if (info.getLastBuiltTree() == null) + continue; + + // Add to the correct list of builders info and add to the configuration names + String configName = info.getConfigName() == null ? activeConfigName : info.getConfigName(); + if (configName.equals(activeConfigName)) { + // Serializes the active configurations's build tree + // TODO could probably do better by serializing the 'oldest' tree + builderInfos.add(info); + configNames.add(configName); + trees.add(info.getLastBuiltTree()); + } else { + additionalBuilderInfos.add(info); + additionalConfigNames.add(configName); + additionalTrees.add(info.getLastBuiltTree()); + } + } + } + } + } + + /** + * Attempts to save plugin info, builder info and build states for all projects + * in the workspace. + * + * The following is written to the output stream: + *
    + *
  • Workspace information
  • + *
  • A list of plugin info
  • + *
  • Builder info for all the builders for each project's active build config
  • + *
  • Workspace trees for all plugins and builders
  • + *
  • And since 3.7:
  • + *
  • Builder info for all the builders of all the other project's buildConfigs
  • + *
  • The names of the buildConfigs for each of the builders
  • + *
+ * This format is designed to work with WorkspaceTreeReader versions 2. + * + * @see WorkspaceTreeReader_2 + */ + protected void writeTree(Map statesToSave, DataOutputStream output, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + boolean wasImmutable = false; + try { + // Create an array of trees to save. Ensure that the current one is in the list + ElementTree current = workspace.getElementTree(); + wasImmutable = current.isImmutable(); + current.immutable(); + ArrayList trees = new ArrayList(statesToSave.size() * 2); // pick a number + monitor.worked(Policy.totalWork * 10 / 100); + + // write out the workspace fields + writeWorkspaceFields(output, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + // save plugin info + output.writeInt(statesToSave.size()); // write the number of plugins we are saving + for (Map.Entry entry : statesToSave.entrySet()) { + String pluginId = entry.getKey(); + output.writeUTF(pluginId); + trees.add(entry.getValue()); // tree + updateDeltaExpiration(pluginId); + } + monitor.worked(Policy.totalWork * 10 / 100); + + // Get the the builder info and configuration names, and add all the associated workspace trees in the correct order + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List builderInfos = new ArrayList(projects.length * 2); + List configNames = new ArrayList(projects.length); + List additionalTrees = new ArrayList(projects.length * 2); + List additionalBuilderInfos = new ArrayList(projects.length * 2); + List additionalConfigNames = new ArrayList(projects.length); + for (int i = 0; i < projects.length; i++) + getTreesToSave(projects[i], trees, builderInfos, configNames, additionalTrees, additionalBuilderInfos, additionalConfigNames); + + // Save the version 2 builders info + writeBuilderPersistentInfo(output, builderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 10 / 100)); + + // Builder infos of non-active configurations are persisted after the active + // configuration's builder infos. So, their trees have to follow the same order. + trees.addAll(additionalTrees); + + // add the current tree in the list as the last tree in the chain + trees.add(current); + + /* save the forest! */ + ElementTreeWriter writer = new ElementTreeWriter(this); + ElementTree[] treesToSave = trees.toArray(new ElementTree[trees.size()]); + writer.writeDeltaChain(treesToSave, Path.ROOT, ElementTreeWriter.D_INFINITE, output, ResourceComparator.getSaveComparator()); + monitor.worked(Policy.totalWork * 40 / 100); + + // Since 3.7: Save the additional builders info + writeBuilderPersistentInfo(output, additionalBuilderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 10 / 100)); + + // Save the configuration names for the builders in the order they were saved + for (Iterator it = configNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + for (Iterator it = additionalConfigNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + } finally { + if (!wasImmutable) + workspace.newWorkingTree(); + } + } finally { + monitor.done(); + } + } + + /** + * Attempts to save all the trees for the given project. This includes the current + * workspace tree and a tree for each builder that has previously built state information. + * + * The following is written to the output stream: + *
    + *
  • Builder info for all the builders for the project's active build configuration
  • + *
  • Workspace trees for all the project's builders
  • + *
  • Since 3.7:
  • + *
  • Builder info for all the builders of all the other project's buildConfigs
  • + *
  • Name of the project's buildConfigs
  • + *
+ * This format is designed to work with WorkspaceTreeReader versions 2. + * + * @throws IOException if anything went wrong during save. + * @see WorkspaceTreeReader_2 + */ + protected void writeTree(Project project, DataOutputStream output, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + boolean wasImmutable = false; + try { + // Create an array of trees to save and ensure that the current one is immutable before we add other trees + ElementTree current = workspace.getElementTree(); + wasImmutable = current.isImmutable(); + current.immutable(); + List trees = new ArrayList(2); + monitor.worked(Policy.totalWork * 10 / 100); + + // Get the the builder info and configuration names, and add all the associated workspace trees in the correct order + List configNames = new ArrayList(5); + List builderInfos = new ArrayList(5); + List additionalConfigNames = new ArrayList(5); + List additionalBuilderInfos = new ArrayList(5); + List additionalTrees = new ArrayList(5); + getTreesToSave(project, trees, builderInfos, configNames, additionalTrees, additionalBuilderInfos, additionalConfigNames); + + // Save the version 2 builders info + writeBuilderPersistentInfo(output, builderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 20 / 100)); + + // Builder infos of non-active configurations are persisted after the active + // configuration's builder infos. So, their trees have to follow the same order. + trees.addAll(additionalTrees); + + // Add the current tree in the list as the last tree in the chain + trees.add(current); + + // Save the trees + ElementTreeWriter writer = new ElementTreeWriter(this); + ElementTree[] treesToSave = trees.toArray(new ElementTree[trees.size()]); + writer.writeDeltaChain(treesToSave, project.getFullPath(), ElementTreeWriter.D_INFINITE, output, ResourceComparator.getSaveComparator()); + monitor.worked(Policy.totalWork * 50 / 100); + + // Since 3.7: Save the builders info and get the workspace trees associated with those builders + writeBuilderPersistentInfo(output, additionalBuilderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 20 / 100)); + + // Save configuration names for the builders in the order they were saved + for (Iterator it = configNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + for (Iterator it = additionalConfigNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + } finally { + if (!wasImmutable) + workspace.newWorkingTree(); + } + } finally { + monitor.done(); + } + } + + protected void writeTree(Project project, int depth) throws CoreException { + long start = System.currentTimeMillis(); + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(project, true); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + try { + SafeFileOutputStream safe = new SafeFileOutputStream(treeLocation.toOSString(), tempLocation.toOSString()); + DataOutputStream output = new DataOutputStream(safe); + try { + output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeTree(project, output, null); + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (IOException e) { + String msg = NLS.bind(Messages.resources_writeMeta, project.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, treeLocation, msg, e); + } + if (Policy.DEBUG_SAVE_TREE) + Policy.debug("Save tree for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + protected void writeWorkspaceFields(DataOutputStream output, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + // save the next node id + output.writeLong(workspace.nextNodeId); + // save the modification stamp (no longer used) + output.writeLong(0L); + // save the marker id counter + output.writeLong(workspace.nextMarkerId); + // save the registered sync partners in the synchronizer + ((Synchronizer) workspace.getSynchronizer()).savePartners(output); + } finally { + monitor.done(); + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java new file mode 100644 index 0000000000..6b3c24c305 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.internal.events.ResourceDelta; +import org.eclipse.core.internal.events.ResourceDeltaFactory; +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.ISchedulingRule; + +/** + * Standard implementation of the ISavedState interface. + */ +public class SavedState implements ISavedState { + ElementTree oldTree; + ElementTree newTree; + SafeFileTable fileTable; + String pluginId; + Workspace workspace; + + SavedState(Workspace workspace, String pluginId, ElementTree oldTree, ElementTree newTree) throws CoreException { + this.workspace = workspace; + this.pluginId = pluginId; + this.newTree = newTree; + this.oldTree = oldTree; + this.fileTable = restoreFileTable(); + } + + void forgetTrees() { + newTree = null; + oldTree = null; + workspace.saveManager.clearDeltaExpiration(pluginId); + } + + @Override + public int getSaveNumber() { + return workspace.getSaveManager().getSaveNumber(pluginId); + } + + protected SafeFileTable getFileTable() { + return fileTable; + } + + protected SafeFileTable restoreFileTable() throws CoreException { + if (fileTable == null) + fileTable = new SafeFileTable(pluginId); + return fileTable; + } + + @Override + public IPath lookup(IPath file) { + return getFileTable().lookup(file); + } + + @Override + public IPath[] getFiles() { + return getFileTable().getFiles(); + } + + @Override + public void processResourceChangeEvents(IResourceChangeListener listener) { + try { + final ISchedulingRule rule = workspace.getRoot(); + try { + workspace.prepareOperation(rule, null); + if (oldTree == null || newTree == null) + return; + workspace.beginOperation(true); + ResourceDelta delta = ResourceDeltaFactory.computeDelta(workspace, oldTree, newTree, Path.ROOT, -1); + forgetTrees(); // free trees to prevent memory leak + workspace.getNotificationManager().broadcastChanges(listener, IResourceChangeEvent.POST_BUILD, delta); + } finally { + workspace.endOperation(rule, false, null); + } + } catch (CoreException e) { + // this is unlikely to happen, so, just log it + Policy.log(e); + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java new file mode 100644 index 0000000000..995a40e857 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * 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.resources; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.osgi.util.NLS; + +/** + * This class is used to read sync info from disk. Subclasses implement + * version specific reading code. + */ +public class SyncInfoReader { + protected Workspace workspace; + protected Synchronizer synchronizer; + + public SyncInfoReader(Workspace workspace, Synchronizer synchronizer) { + super(); + this.workspace = workspace; + this.synchronizer = synchronizer; + } + + /** + * Returns the appropriate reader for the given version. + */ + protected SyncInfoReader getReader(int formatVersion) throws IOException { + switch (formatVersion) { + case 2 : + return new SyncInfoReader_2(workspace, synchronizer); + case 3 : + return new SyncInfoReader_3(workspace, synchronizer); + default : + throw new IOException(NLS.bind(Messages.resources_format, new Integer(formatVersion))); + } + } + + public void readPartners(DataInputStream input) throws CoreException { + try { + int size = input.readInt(); + Set registry = new HashSet(size); + for (int i = 0; i < size; i++) { + String qualifier = input.readUTF(); + String local = input.readUTF(); + registry.add(new QualifiedName(qualifier, local)); + } + synchronizer.setRegistry(registry); + } catch (IOException e) { + String message = NLS.bind(Messages.resources_readSync, e); + throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, message)); + } + } + + public void readSyncInfo(DataInputStream input) throws IOException, CoreException { + // dispatch to the appropriate reader depending + // on the version of the file + int formatVersion = readVersionNumber(input); + SyncInfoReader reader = getReader(formatVersion); + reader.readSyncInfo(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/SyncInfoReader_2.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java new file mode 100644 index 0000000000..29a4e644cf --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * 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.ArrayList; +import java.util.List; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.ObjectMap; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * This class is used to read sync info from disk. This is the implementation + * for reading files with version number 2. + */ +public class SyncInfoReader_2 extends SyncInfoReader { + + // for sync info + public static final int INDEX = 1; + public static final int QNAME = 2; + + public SyncInfoReader_2(Workspace workspace, Synchronizer synchronizer) { + super(workspace, synchronizer); + } + + /** + * SAVE_FILE -> VERSION_ID RESOURCE* + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> TYPE BYTES + * TYPE -> INDEX | QNAME + * INDEX -> int int + * QNAME -> int String + * BYTES -> byte[] + * + */ + @Override + public void readSyncInfo(DataInputStream input) throws IOException, CoreException { + try { + List readPartners = new ArrayList(5); + while (true) { + IPath path = new Path(input.readUTF()); + readSyncInfo(path, input, readPartners); + } + } catch (EOFException e) { + // ignore end of file + } + } + + private void readSyncInfo(IPath path, DataInputStream input, List readPartners) throws IOException, CoreException { + int size = input.readInt(); + ObjectMap table = new ObjectMap(size); + for (int i = 0; i < size; i++) { + QualifiedName name = null; + int type = input.readInt(); + switch (type) { + case QNAME : + String qualifier = input.readUTF(); + String local = input.readUTF(); + name = new QualifiedName(qualifier, local); + readPartners.add(name); + break; + case INDEX : + name = readPartners.get(input.readInt()); + break; + default : + //if we get here then the sync info file is corrupt + String msg = NLS.bind(Messages.resources_readSync, path == null ? "" : path.toString()); //$NON-NLS-1$ + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, path, msg, null); + } + // read the bytes + int length = input.readInt(); + byte[] bytes = new byte[length]; + input.readFully(bytes); + // put them in the table + table.put(name, bytes); + } + // set the table on the resource info + ResourceInfo info = workspace.getResourceInfo(path, true, false); + if (info == null) + return; + info.setSyncInfo(table); + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java new file mode 100644 index 0000000000..a29f8fc31e --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * 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.ArrayList; +import java.util.List; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.ObjectMap; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * This class is used to read sync info from disk. This is the implementation + * for reading files with version number 3. + */ +public class SyncInfoReader_3 extends SyncInfoReader { + + // for sync info + public static final byte INDEX = 1; + public static final byte QNAME = 2; + + public SyncInfoReader_3(Workspace workspace, Synchronizer synchronizer) { + super(workspace, synchronizer); + } + + /** + * SAVE_FILE -> VERSION_ID RESOURCE+ + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> TYPE BYTES + * TYPE -> INDEX | QNAME + * INDEX -> byte int + * QNAME -> byte String + * BYTES -> byte[] + * + */ + @Override + public void readSyncInfo(DataInputStream input) throws IOException, CoreException { + try { + List readPartners = new ArrayList(5); + while (true) { + IPath path = new Path(input.readUTF()); + readSyncInfo(path, input, readPartners); + } + } catch (EOFException e) { + // ignore end of file + } + } + + private void readSyncInfo(IPath path, DataInputStream input, List readPartners) throws IOException, CoreException { + int size = input.readInt(); + ObjectMap table = new ObjectMap(size); + for (int i = 0; i < size; i++) { + QualifiedName name = null; + byte type = input.readByte(); + switch (type) { + case QNAME : + String qualifier = input.readUTF(); + String local = input.readUTF(); + name = new QualifiedName(qualifier, local); + readPartners.add(name); + break; + case INDEX : + name = readPartners.get(input.readInt()); + break; + default : + //if we get here then the sync info file is corrupt + String msg = NLS.bind(Messages.resources_readSync, path == null ? "" : path.toString()); //$NON-NLS-1$ + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, path, msg, null); + } + // read the bytes + int length = input.readInt(); + byte[] bytes = new byte[length]; + input.readFully(bytes); + // put them in the table + table.put(name, bytes); + } + // set the table on the resource info + ResourceInfo info = workspace.getResourceInfo(path, true, false); + if (info == null) + return; + info.setSyncInfo(table); + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java new file mode 100644 index 0000000000..eab437baa5 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.osgi.util.NLS; + +public class SyncInfoSnapReader { + protected Workspace workspace; + protected Synchronizer synchronizer; + + public SyncInfoSnapReader(Workspace workspace, Synchronizer synchronizer) { + super(); + this.workspace = workspace; + this.synchronizer = synchronizer; + } + + /** + * Returns the appropriate reader for the given version. + */ + protected SyncInfoSnapReader getReader(int formatVersion) throws IOException { + switch (formatVersion) { + case 3 : + return new SyncInfoSnapReader_3(workspace, synchronizer); + default : + throw new IOException(NLS.bind(Messages.resources_format, new Integer(formatVersion))); + } + } + + public void readSyncInfo(DataInputStream input) throws IOException { + // dispatch to the appropriate reader depending + // on the version of the file + int formatVersion = readVersionNumber(input); + SyncInfoSnapReader reader = getReader(formatVersion); + reader.readSyncInfo(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/SyncInfoSnapReader_3.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java new file mode 100644 index 0000000000..d4a2684f22 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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 org.eclipse.core.internal.utils.ObjectMap; +import org.eclipse.core.runtime.*; + +public class SyncInfoSnapReader_3 extends SyncInfoSnapReader { + + public SyncInfoSnapReader_3(Workspace workspace, Synchronizer synchronizer) { + super(workspace, synchronizer); + } + + private ObjectMap internalReadSyncInfo(DataInputStream input) throws IOException { + int size = input.readInt(); + ObjectMap map = new ObjectMap(size); + for (int i = 0; i < size; i++) { + // read the qualified name + String qualifier = input.readUTF(); + String local = input.readUTF(); + QualifiedName name = new QualifiedName(qualifier, local); + // read the bytes + int length = input.readInt(); + byte[] bytes = new byte[length]; + input.readFully(bytes); + // put them in the table + map.put(name, bytes); + } + return map; + } + + /** + * SNAP_FILE -> [VERSION_ID RESOURCE]* + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> QNAME BYTES + * QNAME -> String String + * BYTES -> byte[] + */ + @Override + public void readSyncInfo(DataInputStream input) throws IOException { + IPath path = new Path(input.readUTF()); + ObjectMap map = internalReadSyncInfo(input); + // set the table on the resource info + ResourceInfo info = workspace.getResourceInfo(path, true, false); + if (info == null) + return; + info.setSyncInfo(map); + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java new file mode 100644 index 0000000000..55792833f2 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.resources; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; +import org.eclipse.core.internal.watson.IPathRequestor; +import org.eclipse.core.runtime.QualifiedName; + +public class SyncInfoWriter { + protected Synchronizer synchronizer; + protected Workspace workspace; + + // version number + public static final int SYNCINFO_SAVE_VERSION = 3; + public static final int SYNCINFO_SNAP_VERSION = 3; + + // for sync info + public static final byte INDEX = 1; + public static final byte QNAME = 2; + + public SyncInfoWriter(Workspace workspace, Synchronizer synchronizer) { + super(); + this.workspace = workspace; + this.synchronizer = synchronizer; + } + + public void savePartners(DataOutputStream output) throws IOException { + Set registry = synchronizer.getRegistry(); + output.writeInt(registry.size()); + for (Iterator i = registry.iterator(); i.hasNext();) { + QualifiedName qname = i.next(); + output.writeUTF(qname.getQualifier()); + output.writeUTF(qname.getLocalName()); + } + } + + /** + * SAVE_FILE -> VERSION_ID RESOURCE+ + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> TYPE BYTES + * TYPE -> INDEX | QNAME + * INDEX -> byte int + * QNAME -> byte String + * BYTES -> byte[] + */ + public void saveSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List writtenPartners) throws IOException { + Map table = info.getSyncInfo(false); + if (table == null) + return; + // if this is the first sync info that we have written, then + // write the version id for the file. + if (output.size() == 0) + output.writeInt(SYNCINFO_SAVE_VERSION); + output.writeUTF(requestor.requestPath().toString()); + output.writeInt(table.size()); + for (Map.Entry entry : table.entrySet()) { + QualifiedName name = entry.getKey(); + // if we have already written the partner name once, then write an integer + // constant to represent it instead to remove duplication + int index = writtenPartners.indexOf(name); + if (index == -1) { + // FIXME: what to do about null qualifier? + output.writeByte(QNAME); + output.writeUTF(name.getQualifier()); + output.writeUTF(name.getLocalName()); + writtenPartners.add(name); + } else { + output.writeByte(INDEX); + output.writeInt(index); + } + byte[] bytes = (byte[]) entry.getValue(); + output.writeInt(bytes.length); + output.write(bytes); + } + } + + /** + * SNAP_FILE -> [VERSION_ID RESOURCE]* + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> QNAME BYTES + * QNAME -> String String + * BYTES -> byte[] + */ + public void snapSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { + if (!info.isSet(ICoreConstants.M_SYNCINFO_SNAP_DIRTY)) + return; + Map table = info.getSyncInfo(false); + if (table == null) + return; + // write the version id for the snapshot. + output.writeInt(SYNCINFO_SNAP_VERSION); + output.writeUTF(requestor.requestPath().toString()); + output.writeInt(table.size()); + for (Map.Entry entry : table.entrySet()) { + QualifiedName name = entry.getKey(); + output.writeUTF(name.getQualifier()); + output.writeUTF(name.getLocalName()); + byte[] bytes = (byte[]) entry.getValue(); + output.writeInt(bytes.length); + output.write(bytes); + } + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java new file mode 100644 index 0000000000..919e066008 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java @@ -0,0 +1,284 @@ +/******************************************************************************* + * 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 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +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.IPathRequestor; +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 Synchronizer implements ISynchronizer { + protected Workspace workspace; + protected SyncInfoWriter writer; + + // Registry of sync partners. Set of qualified names. + protected Set registry = new HashSet(5); + + public Synchronizer(Workspace workspace) { + super(); + this.workspace = workspace; + this.writer = new SyncInfoWriter(workspace, this); + } + + /** + * @see ISynchronizer#accept(QualifiedName, IResource, IResourceVisitor, int) + */ + @Override + public void accept(QualifiedName partner, IResource resource, IResourceVisitor visitor, int depth) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(resource != null); + Assert.isLegal(visitor != null); + + // if we don't have sync info for the given identifier, then skip it + if (getSyncInfo(partner, resource) != null) { + // visit the resource and if the visitor says to stop the recursion then return + if (!visitor.visit(resource)) + return; + } + + // adjust depth if necessary + if (depth == IResource.DEPTH_ZERO || resource.getType() == IResource.FILE) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.DEPTH_ZERO; + + // otherwise recurse over the children + IResource[] children = ((IContainer) resource).members(); + for (int i = 0; i < children.length; i++) + accept(partner, children[i], visitor, depth); + } + + /** + * @see ISynchronizer#add(QualifiedName) + */ + @Override + public void add(QualifiedName partner) { + Assert.isLegal(partner != null); + registry.add(partner); + } + + /** + * @see ISynchronizer#flushSyncInfo(QualifiedName, IResource, int) + */ + @Override + public void flushSyncInfo(final QualifiedName partner, final IResource root, final int depth) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(root != null); + + IWorkspaceRunnable body = new IWorkspaceRunnable() { + @Override + public void run(IProgressMonitor monitor) throws CoreException { + IResourceVisitor visitor = new IResourceVisitor() { + @Override + public boolean visit(IResource resource) throws CoreException { + //only need to flush sync info if there is sync info + if (getSyncInfo(partner, resource) != null) + setSyncInfo(partner, resource, null); + return true; + } + }; + root.accept(visitor, depth, true); + } + }; + workspace.run(body, root, IResource.NONE, null); + } + + /** + * @see ISynchronizer#getPartners() + */ + @Override + public QualifiedName[] getPartners() { + return registry.toArray(new QualifiedName[registry.size()]); + } + + /** + * For use by the serialization code. + */ + protected Set getRegistry() { + return registry; + } + + /** + * @see ISynchronizer#getSyncInfo(QualifiedName, IResource) + */ + @Override + public byte[] getSyncInfo(QualifiedName partner, IResource resource) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(resource != null); + + if (!isRegistered(partner)) { + String message = NLS.bind(Messages.synchronizer_partnerNotRegistered, partner); + throw new ResourceException(new ResourceStatus(IResourceStatus.PARTNER_NOT_REGISTERED, message)); + } + + // namespace check, if the resource doesn't exist then return null + ResourceInfo info = workspace.getResourceInfo(resource.getFullPath(), true, false); + return (info == null) ? null : info.getSyncInfo(partner, true); + } + + protected boolean isRegistered(QualifiedName partner) { + Assert.isLegal(partner != null); + return registry.contains(partner); + } + + /** + * @see #savePartners(DataOutputStream) + */ + public void readPartners(DataInputStream input) throws CoreException { + SyncInfoReader reader = new SyncInfoReader(workspace, this); + reader.readPartners(input); + } + + public void restore(IResource resource, IProgressMonitor monitor) throws CoreException { + // first restore from the last save and then apply any snapshots + restoreFromSave(resource); + restoreFromSnap(resource); + } + + protected void restoreFromSave(IResource resource) throws CoreException { + IPath sourceLocation = workspace.getMetaArea().getSyncInfoLocationFor(resource); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(sourceLocation); + if (!sourceLocation.toFile().exists() && !tempLocation.toFile().exists()) + return; + try { + DataInputStream input = new DataInputStream(new SafeFileInputStream(sourceLocation.toOSString(), tempLocation.toOSString())); + try { + SyncInfoReader reader = new SyncInfoReader(workspace, this); + reader.readSyncInfo(input); + } 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().getSyncInfoSnapshotLocationFor(resource); + if (!sourceLocation.toFile().exists()) + return; + try { + DataInputStream input = new DataInputStream(new SafeChunkyInputStream(sourceLocation.toFile())); + try { + SyncInfoSnapReader reader = new SyncInfoSnapReader(workspace, this); + while (true) + reader.readSyncInfo(input); + } catch (EOFException eof) { + // ignore end of file -- proceed with what we successfully read + } 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)); + } + } + + /** + * @see ISynchronizer#remove(QualifiedName) + */ + @Override + public void remove(QualifiedName partner) { + Assert.isLegal(partner != null); + if (isRegistered(partner)) { + // remove all sync info for this partner + try { + flushSyncInfo(partner, workspace.getRoot(), IResource.DEPTH_INFINITE); + registry.remove(partner); + } catch (CoreException e) { + // XXX: flush needs to be more resilient and not throw exceptions all the time + Policy.log(e); + } + } + } + + public void savePartners(DataOutputStream output) throws IOException { + writer.savePartners(output); + } + + public void saveSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List writtenPartners) throws IOException { + writer.saveSyncInfo(info, requestor, output, writtenPartners); + } + + protected void setRegistry(Set registry) { + this.registry = registry; + } + + /** + * @see ISynchronizer#setSyncInfo(QualifiedName, IResource, byte[]) + */ + @Override + public void setSyncInfo(QualifiedName partner, IResource resource, byte[] info) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(resource != null); + /* + * We use a dedicated sync rule for this operation and not the resoure + * itself directly. This helps to avoid cancelled auto builds when team provider + * trigger a #setSyncInfo from within a build, e.g. when a builder generates code + * into directories, that are monitored by the team provider. + */ + ISchedulingRule syncRule = workspace.getRuleFactory().syncInfoRule(resource); + try { + workspace.prepareOperation(syncRule, null); + workspace.beginOperation(true); + if (!isRegistered(partner)) { + String message = NLS.bind(Messages.synchronizer_partnerNotRegistered, partner); + throw new ResourceException(new ResourceStatus(IResourceStatus.PARTNER_NOT_REGISTERED, message)); + } + // we do not store sync info on the workspace root + if (resource.getType() == IResource.ROOT) + return; + // if the resource doesn't yet exist then create a phantom so we can set the sync info on it + Resource target = (Resource) resource; + ResourceInfo resourceInfo = workspace.getResourceInfo(target.getFullPath(), true, false); + int flags = target.getFlags(resourceInfo); + if (!target.exists(flags, false)) { + if (info == null) + return; + //ensure it is possible to create this resource + target.checkValidPath(target.getFullPath(), target.getType(), false); + Container parent = (Container) target.getParent(); + parent.checkAccessible(parent.getFlags(parent.getResourceInfo(true, false))); + workspace.createResource(target, true); + } + resourceInfo = target.getResourceInfo(true, true); + resourceInfo.setSyncInfo(partner, info); + resourceInfo.incrementSyncInfoGenerationCount(); + resourceInfo.set(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + flags = target.getFlags(resourceInfo); + if (target.isPhantom(flags) && resourceInfo.getSyncInfo(false) == null) { + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.resources_deleteProblem, null); + ((Resource) resource).deleteResource(false, status); + if (!status.isOK()) + throw new ResourceException(status); + } + } finally { + workspace.endOperation(syncRule, false, null); + } + } + + public void snapSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { + writer.snapSyncInfo(info, requestor, output); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java new file mode 100644 index 0000000000..9465e32b2b --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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 java.util.Properties; +import org.eclipse.core.resources.ResourcesPlugin; + +/** + * Provides special internal access to the workspace resource implementation. + * This class is to be used for testing purposes only. + * + * @since 2.0 + */ +public class TestingSupport { + /** + * Returns the save manager's master table. + */ + public static Properties getMasterTable() { + return ((Workspace) ResourcesPlugin.getWorkspace()).getSaveManager().getMasterTable(); + } + + /** + * Blocks the calling thread until background snapshot completes. + * @since 3.0 + */ + public static void waitForSnapshot() { + try { + ((Workspace) ResourcesPlugin.getWorkspace()).getSaveManager().snapshotJob.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + throw new RuntimeException("Interrupted while waiting for snapshot"); //$NON-NLS-1$ + } + } + + /* + * Class cannot be instantiated. + */ + private TestingSupport() { + // not allowed + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java new file mode 100644 index 0000000000..202167e141 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import org.eclipse.core.runtime.Assert; + +/** + * + */ +public class VariableDescription implements Comparable { + private String name; + private String value; + + public VariableDescription() { + this.name = ""; //$NON-NLS-1$ + this.value = ""; //$NON-NLS-1$ + } + + public VariableDescription(String name, String value) { + super(); + Assert.isNotNull(name); + Assert.isNotNull(value); + this.name = name; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (!(o.getClass() == VariableDescription.class)) + return false; + VariableDescription other = (VariableDescription) o; + return name.equals(other.name) && value == other.value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + @Override + public int hashCode() { + return name.hashCode() + value.hashCode(); + } + + public void setName(String name) { + this.name = name; + } + + public void setValue(String value) { + this.value = value; + } + + /** + * Compare string descriptions in a way that sorts them topologically by + * name. + */ + @Override + public int compareTo(VariableDescription that) { + return name.compareTo(that.name); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java new file mode 100644 index 0000000000..672d0dd94c --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2009, 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 - Fix for bug 266712 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.InputStream; +import java.net.URI; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.filesystem.provider.FileStore; +import org.eclipse.core.runtime.*; + +/** + * A file store representing a virtual resource. + * A virtual resource always exists and has no children. + */ +public class VirtualFileStore extends FileStore { + private final URI location; + + public VirtualFileStore(URI location) { + this.location = location; + } + + @Override + public String[] childNames(int options, IProgressMonitor monitor) { + return FileStore.EMPTY_STRING_ARRAY; + } + + @Override + public IFileInfo fetchInfo(int options, IProgressMonitor monitor) { + FileInfo result = new FileInfo(); + result.setDirectory(true); + result.setExists(true); + result.setLastModified(1);//last modified of zero indicates non-existence + return result; + } + + @Override + public void delete(int options, IProgressMonitor monitor) { + //nothing to do - virtual resources don't exist in any physical file system + } + + @Override + public IFileStore getChild(String name) { + return EFS.getNullFileSystem().getStore(new Path(name).makeAbsolute()); + } + + @Override + public String getName() { + return "virtual"; //$NON-NLS-1$ + } + + @Override + public IFileStore getParent() { + return null; + } + + @Override + public void move(IFileStore destination, int options, IProgressMonitor monitor) throws CoreException { + destination.mkdir(EFS.NONE, monitor); + } + + @Override + public InputStream openInputStream(int options, IProgressMonitor monitor) { + return null; + } + + @Override + public URI toURI() { + return location; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java new file mode 100644 index 0000000000..5986962a02 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2009, 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 java.net.URI; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.filesystem.provider.FileSystem; + +/** + * A file system for virtual resources + */ +public class VirtualFileSystem extends FileSystem { + + public VirtualFileSystem() { + super(); + } + + @Override + public IFileStore getStore(URI uri) { + return new VirtualFileStore(uri); + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java new file mode 100644 index 0000000000..f43de9f220 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java @@ -0,0 +1,324 @@ +/******************************************************************************* + * 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.Messages; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.*; + +/** + * The work manager governs concurrent access to the workspace tree. The {@link #lock} + * field is used to protect the workspace tree data structure from concurrent + * write attempts. This is an internal lock that is generally not held while + * client code is running. Scheduling rules are used by client code to obtain + * exclusive write access to a portion of the workspace. + * + * This class also tracks operation state for each thread that is involved in an + * operation. This includes prepared and running operation depth, auto-build + * strategy and cancel state. + */ +public class WorkManager implements IManager { + /** + * Scheduling rule for use during resource change notification. This rule + * must always be allowed to nest within a resource rule of any granularity + * since it is used from within the scope of all resource changing + * operations. The purpose of this rule is two-fold: 1. To prevent other + * resource changing jobs from being scheduled while the notification is + * running 2. To cause an exception if a resource change listener tries to + * begin a resource rule during a notification. This also prevents + * deadlock, because the notification thread owns the workspace lock, and + * threads that own the workspace lock must never block trying to acquire a + * resource rule. + */ + class NotifyRule implements ISchedulingRule { + @Override + public boolean contains(ISchedulingRule rule) { + return (rule instanceof IResource) || rule.getClass().equals(NotifyRule.class); + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + return contains(rule); + } + } + + /** + * Indicates that the last checkIn failed, either due to cancelation or due to the + * workspace tree being locked for modifications (during resource change events). + */ + private final ThreadLocal checkInFailed = new ThreadLocal(); + /** + * Indicates whether any operations have run that may require a build. + */ + private boolean hasBuildChanges = false; + private IJobManager jobManager; + /** + * The primary workspace lock. This lock must be held by any thread + * modifying the workspace tree. + */ + private final ILock lock; + + /** + * The current depth of running nested operations. + */ + private int nestedOperations = 0; + + private NotifyRule notifyRule = new NotifyRule(); + + private boolean operationCanceled = false; + + /** + * The current depth of prepared operations. + */ + private int preparedOperations = 0; + private Workspace workspace; + + public WorkManager(Workspace workspace) { + this.workspace = workspace; + this.jobManager = Job.getJobManager(); + this.lock = jobManager.newLock(); + } + + /** + * Releases the workspace lock without changing the nested operation depth. + * Must be followed eventually by endUnprotected. Any + * beginUnprotected/endUnprotected pair must be done entirely within the + * scope of a checkIn/checkOut pair. Returns the old lock depth. + * @see #endUnprotected(int) + */ + public int beginUnprotected() { + int depth = lock.getDepth(); + for (int i = 0; i < depth; i++) + lock.release(); + return depth; + } + + /** + * An operation calls this method and it only returns when the operation is + * free to run. + */ + public void checkIn(ISchedulingRule rule, IProgressMonitor monitor) throws CoreException { + boolean success = false; + try { + if (workspace.isTreeLocked()) { + String msg = Messages.resources_cannotModify; + throw new ResourceException(IResourceStatus.WORKSPACE_LOCKED, null, msg, null); + } + jobManager.beginRule(rule, monitor); + lock.acquire(); + incrementPreparedOperations(); + success = true; + } finally { + //remember if we failed to check in, so we can avoid check out + if (!success) + checkInFailed.set(Boolean.TRUE); + } + } + + /** + * Returns true if the check in for this thread failed, in which case the + * check out and other end of operation code should not run. + *

+ * The failure flag is reset immediately after calling this method. Subsequent + * calls to this method will indicate no failure (unless a new failure has occurred). + * @return true if the checkIn failed, and false otherwise. + */ + public boolean checkInFailed(ISchedulingRule rule) { + if (checkInFailed.get() != null) { + //clear the failure flag for this thread + checkInFailed.set(null); + //must still end the rule even in the case of failure + if (!workspace.isTreeLocked()) + jobManager.endRule(rule); + return true; + } + return false; + } + + /** + * Inform that an operation has finished. + */ + public synchronized void checkOut(ISchedulingRule rule) { + decrementPreparedOperations(); + rebalanceNestedOperations(); + //reset state if this is the end of a top level operation + if (preparedOperations == 0) + hasBuildChanges = false; + //don't let cancelation of this operation affect other operations + operationCanceled = false; + try { + lock.release(); + } finally { + //end rule in finally in case lock.release throws an exception + jobManager.endRule(rule); + } + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + private void decrementPreparedOperations() { + preparedOperations--; + } + + /** + * Re-acquires the workspace lock that was temporarily released during an + * operation, and restores the old lock depth. + * @see #beginUnprotected() + */ + public void endUnprotected(int depth) { + for (int i = 0; i < depth; i++) + lock.acquire(); + } + + /** + * Returns the work manager's lock + */ + ILock getLock() { + return lock; + } + + /** + * Returns the scheduling rule used during resource change notifications. + */ + public ISchedulingRule getNotifyRule() { + return notifyRule; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + public synchronized int getPreparedOperationDepth() { + return preparedOperations; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + void incrementNestedOperations() { + nestedOperations++; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + private void incrementPreparedOperations() { + preparedOperations++; + } + + /** + * Returns true if the nested operation depth is the same as the prepared + * operation depth, and false otherwise. This method can only be safely + * called from inside a workspace operation. Should NOT be called from + * outside a prepareOperation/endOperation block. + */ + boolean isBalanced() { + return nestedOperations == preparedOperations; + } + + /** + * Returns true if the workspace lock has already been acquired by this + * thread, and false otherwise. + */ + public boolean isLockAlreadyAcquired() { + boolean result = false; + try { + boolean success = lock.acquire(0L); + if (success) { + //if lock depth is greater than one, then we already owned it + // before + result = lock.getDepth() > 1; + lock.release(); + } + } catch (InterruptedException e) { + // ignore + } + return result; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + public void operationCanceled() { + operationCanceled = true; + } + + /** + * Used to make things stable again after an operation has failed between a + * workspace.prepareOperation() and workspace.beginOperation(). This method + * can only be safely called from inside a workspace operation. Should NOT + * be called from outside a prepareOperation/endOperation block. + */ + public void rebalanceNestedOperations() { + nestedOperations = preparedOperations; + } + + /** + * Indicates if the operation that has just completed may potentially + * require a build. + */ + public void setBuild(boolean hasChanges) { + hasBuildChanges = hasBuildChanges || hasChanges; + } + + /** + * This method can only be safely called from inside a workspace operation. + * Should NOT be called from outside a prepareOperation/endOperation block. + */ + public boolean shouldBuild() { + if (hasBuildChanges) { + if (operationCanceled) + return Policy.buildOnCancel; + return true; + } + return false; + } + + @Override + public void shutdown(IProgressMonitor monitor) { + // do nothing + } + + @Override + public void startup(IProgressMonitor monitor) { + jobManager.beginRule(workspace.getRoot(), monitor); + lock.acquire(); + } + + /** + * This method should be called at the end of the workspace startup, even if the startup failed. + * It must be preceded by a call to startup. It releases the primary workspace lock + * and ends applying the workspace rule to this thread. + */ + void postWorkspaceStartup() { + try { + lock.release(); + } finally { + //end rule in finally in case lock.release throws an exception + jobManager.endRule(workspace.getRoot()); + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java new file mode 100644 index 0000000000..11ff37111d --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java @@ -0,0 +1,2558 @@ +/******************************************************************************* + * 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 + * Red Hat Incorporated - loadProjectDescription(InputStream) + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + * Broadcom Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.*; +import org.eclipse.core.internal.localstore.FileSystemResourceManager; +import org.eclipse.core.internal.properties.IPropertyManager; +import org.eclipse.core.internal.properties.PropertyManager2; +import org.eclipse.core.internal.refresh.RefreshManager; +import org.eclipse.core.internal.resources.ComputeProjectOrder.VertexFilter; +import org.eclipse.core.internal.resources.ComputeProjectOrder.VertexOrder; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.internal.watson.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.Bundle; +import org.xml.sax.InputSource; + +/** + * The workspace class is the monolithic nerve center of the resources plugin. + * All interesting functionality stems from this class. + *

+ *

+ * The lifecycle of the resources plugin is encapsulated by the {@link #open(IProgressMonitor)} + * and {@link #close(IProgressMonitor)} methods. A closed workspace is completely + * unusable - any attempt to access or modify interesting workspace state on a closed + * workspace will fail. + *

+ *

+ * All modifications to the workspace occur within the context of a workspace operation. + * A workspace operation is implemented using the following sequence: + *

+ * 	try {
+ *		prepareOperation(...);
+ *		//check preconditions
+ *		beginOperation(...);
+ *		//perform changes
+ *	} finally {
+ *		endOperation(...);
+ *	}
+ * 
+ * Workspace operations can be nested arbitrarily. A "top level" workspace operation + * is an operation that is not nested within another workspace operation in the current + * thread. + * See the javadoc of {@link #prepareOperation(ISchedulingRule, IProgressMonitor)}, + * {@link #beginOperation(boolean)}, and {@link #endOperation(ISchedulingRule, boolean, IProgressMonitor)} + * for more details. + *

+ *

+ * Major areas of functionality are farmed off to various manager classes. Open a + * type hierarchy on {@link IManager} to see all the different managers. Each + * manager is typically referenced three times in this class: Once in {@link #startup(IProgressMonitor)} + * when it is instantiated, once in {@link #shutdown(IProgressMonitor)} when it + * is destroyed, and once in a manager accessor method. + *

+ */ +public class Workspace extends PlatformObject implements IWorkspace, ICoreConstants { + public static final boolean caseSensitive = Platform.OS_MACOSX.equals(Platform.getOS()) ? false : new java.io.File("a").compareTo(new java.io.File("A")) != 0; //$NON-NLS-1$ //$NON-NLS-2$ + + /** + * Work manager should never be accessed directly because accessor + * asserts that workspace is still open. + */ + protected WorkManager _workManager; + protected AliasManager aliasManager; + protected BuildManager buildManager; + protected volatile IBuildConfiguration[] buildOrder = null; + protected CharsetManager charsetManager; + protected ContentDescriptionManager contentDescriptionManager; + /** indicates if the workspace crashed in a previous session */ + protected boolean crashed = false; + protected final IWorkspaceRoot defaultRoot = new WorkspaceRoot(Path.ROOT, this); + protected WorkspacePreferences description; + protected FileSystemResourceManager fileSystemManager; + protected final CopyOnWriteArrayList lifecycleListeners = new CopyOnWriteArrayList(); + protected LocalMetaArea localMetaArea; + /** + * Helper class for performing validation of resource names and locations. + */ + protected final LocationValidator locationValidator = new LocationValidator(this); + protected MarkerManager markerManager; + /** + * The currently installed Move/Delete hook. + */ + protected IMoveDeleteHook moveDeleteHook = null; + protected NatureManager natureManager; + protected FilterTypeManager filterManager; + protected long nextMarkerId = 0; + protected long nextNodeId = 1; + + protected NotificationManager notificationManager; + protected boolean openFlag = false; + protected ElementTree operationTree; // tree at the start of the current operation + protected PathVariableManager pathVariableManager; + protected IPropertyManager propertyManager; + + protected RefreshManager refreshManager; + + /** + * Scheduling rule factory. This field is null if the factory has not been used + * yet. The accessor method should be used rather than accessing this field + * directly. + */ + private IResourceRuleFactory ruleFactory; + + protected SaveManager saveManager; + /** + * File modification validation. If it is true and validator is null, we try/initialize + * validator first time through. If false, there is no validator. + */ + protected boolean shouldValidate = true; + + /** + * Job that performs periodic string pool canonicalization. + */ + private StringPoolJob stringPoolJob; + + /** + * The synchronizer + */ + protected Synchronizer synchronizer; + + /** + * The currently installed team hook. + */ + protected TeamHook teamHook = null; + + /** + * The workspace tree. The tree is an in-memory representation + * of the resources that make up the workspace. The tree caches + * the structure and state of files and directories on disk (their existence + * and last modified times). When external parties make changes to + * the files on disk, this representation becomes out of sync. A local refresh + * reconciles the state of the files on disk with this tree (@link {@link IResource#refreshLocal(int, IProgressMonitor)}). + * The tree is also used to store metadata associated with resources in + * the workspace (markers, properties, etc). + * + * While the ElementTree data structure can handle both concurrent + * reads and concurrent writes, write access to the tree is governed + * by {@link WorkManager}. + */ + protected volatile ElementTree tree; + + /** + * This field is used to control access to the workspace tree during + * resource change notifications. It tracks which thread, if any, is + * in the middle of a resource change notification. This is used to cause + * attempts to modify the workspace during notifications to fail. + */ + protected Thread treeLocked = null; + + /** + * The currently installed file modification validator. + */ + protected IFileModificationValidator validator = null; + + /** + * Data structure for holding the multi-part outcome of + * IWorkspace.computeProjectBuildConfigOrder. + *

+ * This class is not intended to be instantiated by clients. + *

+ * + * @see Workspace#computeProjectBuildConfigOrder(IBuildConfiguration[]) + * @since 3.7 + */ + public static final class ProjectBuildConfigOrder { + /** + * Creates an instance with the given values. + *

+ * This class is not intended to be instantiated by clients. + *

+ * + * @param buildConfigurations initial value of buildConfigurations field + * @param hasCycles initial value of hasCycles field + * @param knots initial value of knots field + */ + public ProjectBuildConfigOrder(IBuildConfiguration[] buildConfigurations, boolean hasCycles, IBuildConfiguration[][] knots) { + this.buildConfigurations = buildConfigurations; + this.hasCycles = hasCycles; + this.knots = knots; + } + + /** + * A list of project buildConfigs ordered so as to honor the build configuration reference + * relationships between these project buildConfigs wherever possible. The elements + * are a subset of the ones passed as the buildConfigurations + * parameter to IWorkspace.computeProjectOrder, where + * inaccessible (closed or non-existent) projects have been omitted. + */ + public IBuildConfiguration[] buildConfigurations; + /** + * Indicates whether any of the accessible project buildConfigs in + * buildConfigurations are involved in non-trivial cycles. + * true if the reference graph contains at least + * one cycle involving two or more of the project buildConfigs in + * buildConfigurations, and false if none of the + * project buildConfigs in buildConfigurations are involved in cycles. + */ + 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 accessible project buildConfigs from buildConfigurations that are + * involved in a cycle of mutually dependent references. + */ + public IBuildConfiguration[][] knots; + } + + // Comparator used to provide a stable ordering of project buildConfigs + private static class BuildConfigurationComparator implements Comparator { + public BuildConfigurationComparator() { + } + + @Override + public int compare(IBuildConfiguration px, IBuildConfiguration py) { + int cmp = py.getProject().getName().compareTo(px.getProject().getName()); + if (cmp == 0) + cmp = py.getName().compareTo(px.getName()); + return cmp; + } + } + + /** + * Deletes all the files and directories from the given root down (inclusive). + * Returns false if we could not delete some file or an exception occurred + * at any point in the deletion. + * Even if an exception occurs, a best effort is made to continue deleting. + */ + public static boolean clear(java.io.File root) { + IFileStore fileStore = EFS.getLocalFileSystem().fromLocalFile(root); + try { + fileStore.delete(EFS.NONE, new NullProgressMonitor()); + } catch (CoreException e) { + return false; + } + return true; + } + + public static WorkspaceDescription defaultWorkspaceDescription() { + return new WorkspaceDescription("Workspace"); //$NON-NLS-1$ + } + + /** + * Returns true if the object at the specified position has any + * other copy in the given array. + */ + private static boolean isDuplicate(Object[] array, int position) { + if (array == null || position >= array.length) + return false; + for (int j = position - 1; j >= 0; j--) + if (array[j].equals(array[position])) + return true; + return false; + } + + public Workspace() { + super(); + localMetaArea = new LocalMetaArea(); + tree = new ElementTree(); + /* tree should only be modified during operations */ + tree.immutable(); + treeLocked = Thread.currentThread(); + tree.setTreeData(newElement(IResource.ROOT)); + } + + /** + * Indicates that a build is about to occur. Broadcasts the necessary + * deltas before the build starts. Note that this will cause POST_BUILD + * to be automatically done at the end of the operation in which + * the build occurs. + */ + protected void aboutToBuild(Object source, int trigger) { + //fire a POST_CHANGE first to ensure everyone is up to date before firing PRE_BUILD + broadcastPostChange(); + broadcastBuildEvent(source, IResourceChangeEvent.PRE_BUILD, trigger); + } + + /** + * Adds a listener for internal workspace lifecycle events. There is no way to + * remove lifecycle listeners. + */ + public void addLifecycleListener(ILifecycleListener listener) { + lifecycleListeners.addIfAbsent(listener); + } + + @Override + public void addResourceChangeListener(IResourceChangeListener listener) { + notificationManager.addListener(listener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE); + } + + @Override + public void addResourceChangeListener(IResourceChangeListener listener, int eventMask) { + notificationManager.addListener(listener, eventMask); + } + + /** + * @deprecated Use {@link #addSaveParticipant(String, ISaveParticipant)} instead + */ + @Deprecated + @Override + public ISavedState addSaveParticipant(Plugin plugin, ISaveParticipant participant) throws CoreException { + Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$ + Assert.isNotNull(participant, "Participant must not be null"); //$NON-NLS-1$ + return saveManager.addParticipant(plugin.getBundle().getSymbolicName(), participant); + } + + @Override + public ISavedState addSaveParticipant(String pluginId, ISaveParticipant participant) throws CoreException { + Assert.isNotNull(pluginId, "Plugin id must not be null"); //$NON-NLS-1$ + Assert.isNotNull(participant, "Participant must not be null"); //$NON-NLS-1$ + return saveManager.addParticipant(pluginId, participant); + } + + public void beginOperation(boolean createNewTree) throws CoreException { + WorkManager workManager = getWorkManager(); + workManager.incrementNestedOperations(); + if (!workManager.isBalanced()) + Assert.isTrue(false, "Operation was not prepared."); //$NON-NLS-1$ + if (workManager.getPreparedOperationDepth() > 1) { + if (createNewTree && tree.isImmutable()) + newWorkingTree(); + return; + } + // stash the current tree as the basis for this operation. + operationTree = tree; + if (createNewTree && tree.isImmutable()) + newWorkingTree(); + } + + public void broadcastBuildEvent(Object source, int type, int buildTrigger) { + ResourceChangeEvent event = new ResourceChangeEvent(source, type, buildTrigger, null); + notificationManager.broadcastChanges(tree, event, false); + } + + /** + * Broadcasts an internal workspace lifecycle event to interested + * internal listeners. + */ + protected void broadcastEvent(LifecycleEvent event) throws CoreException { + for (ILifecycleListener listener : lifecycleListeners) + listener.handleEvent(event); + } + + public void broadcastPostChange() { + ResourceChangeEvent event = new ResourceChangeEvent(this, IResourceChangeEvent.POST_CHANGE, 0, null); + notificationManager.broadcastChanges(tree, event, true); + } + + /** + * Add all IBuildConfigurations reachable from config to the configs collection. + * @param configs collection of configurations to extend + * @param config config to find reachable configurations to. + */ + private void recursivelyAddBuildConfigs(Collection/**/ configs, IBuildConfiguration config) { + try { + IBuildConfiguration[] referenced = config.getProject().getReferencedBuildConfigs(config.getName(), false); + for (int i = 0; i < referenced.length; i++) { + if (configs.contains(referenced[i])) + continue; + configs.add(referenced[i]); + recursivelyAddBuildConfigs(configs, referenced[i]); + } + } catch (CoreException e) { + // Not possible, we've checked that the project + configuration are accessible. + Assert.isTrue(false); + } + } + + @Override + public void build(int trigger, IProgressMonitor monitor) throws CoreException { + buildInternal(EMPTY_BUILD_CONFIG_ARRAY, trigger, true, monitor); + } + + @Override + public void build(IBuildConfiguration[] configs, int trigger, boolean buildReferences, IProgressMonitor monitor) throws CoreException { + if (configs.length == 0) + return; + buildInternal(configs, trigger, buildReferences, monitor); + } + + /** + * Build the passed in configurations or the whole workspace. + * @param configs to build or EMPTY_BUILD_CONFIG_ARRAY for the whole workspace + * @param trigger build trigger + * @param buildReferences transitively build referenced build configurations + */ + private void buildInternal(IBuildConfiguration[] configs, int trigger, boolean buildReferences, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + // Bug 343256 use a relaxed scheduling rule if the config we're building uses a relaxed rule. + // Otherwise fall-back to WR. + boolean relaxed = false; + if (Job.getJobManager().currentRule() == null && configs.length > 0) { + relaxed = true; + for (IBuildConfiguration config : configs) { + ISchedulingRule requested = getBuildManager().getRule(config, trigger, null, null); + if (requested != null && requested.contains(getRoot())) { + relaxed = false; + break; + } + } + } + + // PRE + POST_BUILD, and the build itself are allowed to modify resources, so require the current thread's scheduling rule + // to either contain the WR or be null. Therefore, if not null, ensure it contains the WR rule... + final ISchedulingRule buildRule = getRuleFactory().buildRule(); + final ISchedulingRule rule = relaxed ? null : buildRule; + try { + monitor.beginTask("", Policy.opWork); //$NON-NLS-1$ + try { + try { + // Must run the PRE_BUILD with the WRule held before acquiring WS lock + // Can remove this if we run notifications without the WS lock held: bug 249951 + prepareOperation(rule == null ? buildRule : rule, monitor); + beginOperation(true); + aboutToBuild(this, trigger); + } finally { + if (rule == null) { + endOperation(buildRule, false, monitor); + prepareOperation(rule, monitor); + beginOperation(false); + } + } + IStatus result; + try { + + // Calculate the build-order having called the pre-build notification (which may change build order) + // If configs == EMPTY_BUILD_CONFIG_ARRAY => This is a full workspace build. + IBuildConfiguration[] requestedConfigs = configs; + if (configs == EMPTY_BUILD_CONFIG_ARRAY) { + if (trigger != IncrementalProjectBuilder.CLEAN_BUILD) + configs = getBuildOrder(); + else { + // clean all accessible configurations + List configArr = new ArrayList(); + IProject[] prjs = getRoot().getProjects(); + for (int i = 0; i < prjs.length; i++) + if (prjs[i].isAccessible()) + configArr.addAll(Arrays.asList(prjs[i].getBuildConfigs())); + configs = configArr.toArray(new IBuildConfiguration[configArr.size()]); + } + } else { + // Order the passed in build configurations + resolve references if requested + Set refsList = new HashSet(); + for (int i = 0; i < configs.length; i++) { + // Check project + build configuration are accessible. + if (!configs[i].getProject().isAccessible() || !configs[i].getProject().hasBuildConfig(configs[i].getName())) + continue; + refsList.add(configs[i]); + // Find transitive closure of referenced project buildConfigs + if (buildReferences) + recursivelyAddBuildConfigs(refsList, configs[i]); + } + + // Order the referenced project buildConfigs + ProjectBuildConfigOrder order = computeProjectBuildConfigOrder(refsList.toArray(new IBuildConfiguration[refsList.size()])); + configs = order.buildConfigurations; + } + + result = getBuildManager().build(configs, requestedConfigs, trigger, Policy.subMonitorFor(monitor, Policy.opWork)); + } finally { + // Run the POST_BUILD with the WRule held + if (rule == null) { + endOperation(rule, false, monitor); + prepareOperation(buildRule, monitor); + beginOperation(false); + } + //must fire POST_BUILD if PRE_BUILD has occurred + broadcastBuildEvent(this, IResourceChangeEvent.POST_BUILD, trigger); + } + if (!result.isOK()) + throw new ResourceException(result); + } finally { + //building may close the tree, but we are still inside an operation so open it + if (tree.isImmutable()) + newWorkingTree(); + // Rule will be the build-rule from the POST_BUILD refresh + endOperation(buildRule, false, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + /** + * Returns whether creating executable extensions is acceptable + * at this point in time. In particular, returns false + * when the system bundle is shutting down, which only occurs + * when the entire framework is exiting. + */ + private boolean canCreateExtensions() { + return Platform.getBundle("org.eclipse.osgi").getState() != Bundle.STOPPING; //$NON-NLS-1$ + } + + @Override + public void checkpoint(boolean build) { + try { + final ISchedulingRule rule = getWorkManager().getNotifyRule(); + try { + prepareOperation(rule, null); + beginOperation(true); + broadcastPostChange(); + } finally { + endOperation(rule, build, null); + } + } catch (CoreException e) { + Policy.log(e.getStatus()); + } + } + + /** + * Closes this workspace; ignored if this workspace is not open. + * The state of this workspace is not saved before the workspace + * is shut down. + *

+ * If the workspace was saved immediately prior to closing, + * it will have the same set of projects + * (open or closed) when reopened for a subsequent session. + * Otherwise, closing a workspace may lose some or all of the + * changes made since the last save or snapshot. + *

+ *

+ * Note that session properties are discarded when a workspace is closed. + *

+ *

+ * 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 and cancellation are not desired + * @exception CoreException if the workspace could not be shutdown. + */ + public void close(IProgressMonitor monitor) throws CoreException { + //nothing to do if the workspace failed to open + if (!isOpen()) + return; + monitor = Policy.monitorFor(monitor); + try { + String msg = Messages.resources_closing_0; + int rootCount = tree.getChildCount(Path.ROOT); + monitor.beginTask(msg, rootCount + 2); + monitor.subTask(msg); + //this operation will never end because the world is going away + try { + stringPoolJob.cancel(); + //shutdown save manager now so a last snapshot can be taken before we close + //note: you can't call #save() from within a nested operation + saveManager.shutdown(null); + saveManager.reportSnapshotRequestor(); + prepareOperation(getRoot(), monitor); + //shutdown notification first to avoid calling third parties during shutdown + notificationManager.shutdown(null); + beginOperation(true); + IProject[] projects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) { + //notify managers of closing so they can cleanup + broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, projects[i])); + monitor.worked(1); + } + //empty the workspace tree so we leave in a clean state + deleteResource(getRoot()); + openFlag = false; + // endOperation not needed here + } finally { + // Shutdown needs to be executed regardless of failures + shutdown(Policy.subMonitorFor(monitor, 2, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)); + } + } finally { + //release the scheduling rule to be a good job citizen + Job.getJobManager().endRule(getRoot()); + monitor.done(); + } + } + + /** + * Computes the global total ordering of all open projects in the + * workspace based on project references. If an existing and open project P + * references another existing and open project Q also included in the list, + * then Q should come before P in the resulting ordering. Closed and non- + * existent projects are ignored, and will not appear in the result. References + * to non-existent or closed projects are also ignored, as are any self- + * references. + *

+ * When there are choices, the choice is made in a reasonably stable way. For + * example, given an arbitrary choice between two projects, the one with the + * lower collating project name is usually selected. + *

+ *

+ * When the project reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references P1, + * P4 references P3, and P2 and P3 reference each other, then exactly one of the + * relationships between P2 and P3 will have to be ignored. The outcome will be + * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains + * complete details of any cycles present. + *

+ * + * @return result describing the global project order + * @since 2.1 + */ + private VertexOrder computeFullProjectOrder() { + // determine the full set of accessible projects in the workspace + // order the set in descending alphabetical order of project name + SortedSet allAccessibleProjects = new TreeSet(new Comparator() { + @Override + public int compare(IProject px, IProject py) { + return py.getName().compareTo(px.getName()); + } + }); + IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + // List edges + List edges = new ArrayList(allProjects.length); + for (int i = 0; i < allProjects.length; i++) { + Project project = (Project) allProjects[i]; + // ignore projects that are not accessible + if (!project.isAccessible()) + continue; + ProjectDescription desc = project.internalGetDescription(); + if (desc == null) + continue; + //obtain both static and dynamic project references + IProject[] refs = desc.getAllReferences(false); + allAccessibleProjects.add(project); + for (int j = 0; j < refs.length; j++) { + IProject ref = refs[j]; + // ignore self references and references to projects that are not accessible + if (ref.isAccessible() && !ref.equals(project)) + edges.add(new IProject[] {project, ref}); + } + } + return ComputeProjectOrder.computeVertexOrder(allAccessibleProjects, edges); + } + + /** + * Computes the global total ordering of all open projects' active buildConfigs in the + * workspace based on build configuration references. If an existing and open project's build config P + * references another existing and open project's build config Q, then Q should come before P + * in the resulting ordering. If a build config references a non-active build config it is + * added to the resulting ordered list. Closed and non-existent projects/buildConfigs are + * ignored, and will not appear in the result. References to non-existent or closed + * projects/buildConfigs are also ignored, as are any self-references. + *

+ * When there are choices, the choice is made in a reasonably stable way. For + * example, given an arbitrary choice between two project buildConfigs, the one with the + * lower collating project name and build config name will appear earlier in the list. + *

+ *

+ * When the build configuration reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references P1, + * P4 references P3, and P2 and P3 reference each other, then exactly one of the + * relationships between P2 and P3 will have to be ignored. The outcome will be + * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains + * complete details of any cycles present. + *

+ * + * @return result describing the global active build configuration order + */ + private VertexOrder computeActiveBuildConfigOrder() { + // Determine the full set of accessible active project buildConfigs in the workspace, + // and all the accessible project buildConfigs that they reference. This forms a set + // of all the project buildConfigs that will be returned. + // Order the set in descending alphabetical order of project name then build config name, + // as a secondary sort applied after sorting based on references, to achieve a stable + // ordering. + SortedSet allAccessibleBuildConfigs = new TreeSet(new BuildConfigurationComparator()); + + // For each project's active build config, perform a depth first search in the reference graph + // rooted at that build config. + // This generates the required subset of the reference graph that is required to order all + // the dependencies of the active project buildConfigs. + IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List edges = new ArrayList(allProjects.length); + + for (int i = 0; i < allProjects.length; i++) { + Project project = (Project) allProjects[i]; + // Ignore projects that are not accessible + if (!project.isAccessible()) + continue; + + // If the active build configuration hasn't already been explored + // perform a depth first search rooted at it + if (!allAccessibleBuildConfigs.contains(project.internalGetActiveBuildConfig())) { + allAccessibleBuildConfigs.add(project.internalGetActiveBuildConfig()); + Stack stack = new Stack(); + stack.push(project.internalGetActiveBuildConfig()); + + while (!stack.isEmpty()) { + IBuildConfiguration buildConfiguration = stack.pop(); + + // Add all referenced buildConfigs from the current configuration + // (it is guaranteed to be accessible as it was pushed onto the stack) + Project subProject = (Project) buildConfiguration.getProject(); + IBuildConfiguration[] refs = subProject.internalGetReferencedBuildConfigs(buildConfiguration.getName(), false); + for (int j = 0; j < refs.length; j++) { + IBuildConfiguration ref = refs[j]; + + // Ignore self references and references to projects that are not accessible + if (ref.equals(buildConfiguration)) + continue; + + // Add the referenced accessible configuration + edges.add(new IBuildConfiguration[] {buildConfiguration, ref}); + + // If we have already explored the referenced configuration, don't explore it again + if (allAccessibleBuildConfigs.contains(ref)) + continue; + + allAccessibleBuildConfigs.add(ref); + + // Push the referenced configuration onto the stack so that it is explored by the depth first search + stack.push(ref); + } + } + } + } + return ComputeProjectOrder.computeVertexOrder(allAccessibleBuildConfigs, edges); + } + + /** + * Computes the global total ordering of all project buildConfigs in the workspace based + * on build config references. If an existing and open build config P + * references another existing and open project build config Q, then Q should come before P + * in the resulting ordering. Closed and non-existent projects/buildConfigs are + * ignored, and will not appear in the result. References to non-existent or closed + * projects/buildConfigs are also ignored, as are any self-references. + *

+ * When there are choices, the choice is made in a reasonably stable way. For + * example, given an arbitrary choice between two project buildConfigs, the one with the + * lower collating project name and build config name will appear earlier in the list. + *

+ *

+ * When the build config reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references P1, + * P4 references P3, and P2 and P3 reference each other, then exactly one of the + * relationships between P2 and P3 will have to be ignored. The outcome will be + * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains + * complete details of any cycles present. + *

+ * + * @return result describing the global project build configuration order + */ + private VertexOrder computeFullBuildConfigOrder() { + // Compute the order for all accessible project buildConfigs + SortedSet allAccessibleBuildConfigurations = new TreeSet(new BuildConfigurationComparator()); + + IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List edges = new ArrayList(allProjects.length); + + for (int i = 0; i < allProjects.length; i++) { + Project project = (Project) allProjects[i]; + // Ignore projects that are not accessible + if (!project.isAccessible()) + continue; + + IBuildConfiguration[] configs = project.internalGetBuildConfigs(false); + for (int j = 0; j < configs.length; j++) { + IBuildConfiguration config = configs[j]; + allAccessibleBuildConfigurations.add(config); + IBuildConfiguration[] refs = project.internalGetReferencedBuildConfigs(config.getName(), false); + for (int k = 0; k < refs.length; k++) { + IBuildConfiguration ref = refs[k]; + + // Ignore self references + if (ref.equals(config)) + continue; + + // Add the reference to the set of reachable configs + add an edge + allAccessibleBuildConfigurations.add(ref); + edges.add(new IBuildConfiguration[] {config, ref}); + } + } + } + return ComputeProjectOrder.computeVertexOrder(allAccessibleBuildConfigurations, edges); + } + + private static ProjectOrder vertexOrderToProjectOrder(VertexOrder order) { + IProject[] projects = new IProject[order.vertexes.length]; + System.arraycopy(order.vertexes, 0, projects, 0, order.vertexes.length); + IProject[][] knots = new IProject[order.knots.length][]; + for (int i = 0; i < order.knots.length; i++) { + knots[i] = new IProject[order.knots[i].length]; + System.arraycopy(order.knots[i], 0, knots[i], 0, order.knots[i].length); + } + return new ProjectOrder(projects, order.hasCycles, knots); + } + + private static ProjectBuildConfigOrder vertexOrderToProjectBuildConfigOrder(VertexOrder order) { + IBuildConfiguration[] buildConfigs = new IBuildConfiguration[order.vertexes.length]; + System.arraycopy(order.vertexes, 0, buildConfigs, 0, order.vertexes.length); + IBuildConfiguration[][] knots = new IBuildConfiguration[order.knots.length][]; + for (int i = 0; i < order.knots.length; i++) { + knots[i] = new IBuildConfiguration[order.knots[i].length]; + System.arraycopy(order.knots[i], 0, knots[i], 0, order.knots[i].length); + } + return new ProjectBuildConfigOrder(buildConfigs, order.hasCycles, knots); + } + + @Deprecated + @Override + public IProject[][] computePrerequisiteOrder(IProject[] targets) { + return computePrerequisiteOrder1(targets); + } + + /* + * Compatible reimplementation of + * IWorkspace.computePrerequisiteOrder using + * IWorkspace.computeProjectOrder. + * + * @since 2.1 + */ + private IProject[][] computePrerequisiteOrder1(IProject[] projects) { + IWorkspace.ProjectOrder r = computeProjectOrder(projects); + if (!r.hasCycles) { + return new IProject[][] {r.projects, new IProject[0]}; + } + // when there are cycles, we need to remove all knotted projects from + // r.projects to form result[0] and merge all knots to form result[1] + // Set bad + Set bad = new HashSet(); + // Set bad + Set keepers = new HashSet(Arrays.asList(r.projects)); + for (int i = 0; i < r.knots.length; i++) { + IProject[] knot = r.knots[i]; + for (int j = 0; j < knot.length; j++) { + IProject project = knot[j]; + // keep only selected projects in knot + if (keepers.contains(project)) { + bad.add(project); + } + } + } + IProject[] result2 = new IProject[bad.size()]; + bad.toArray(result2); + // List p + List p = new LinkedList(); + p.addAll(Arrays.asList(r.projects)); + for (Iterator it = p.listIterator(); it.hasNext();) { + IProject project = it.next(); + if (bad.contains(project)) { + // remove knotted projects from the main answer + it.remove(); + } + } + IProject[] result1 = new IProject[p.size()]; + p.toArray(result1); + return new IProject[][] {result1, result2}; + } + + @Override + public ProjectOrder computeProjectOrder(IProject[] projects) { + // Compute the full project order for all accessible projects + VertexOrder fullProjectOrder = computeFullProjectOrder(); + + // Create a filter to remove all projects that are not in the list asked for + final Set projectSet = new HashSet(projects.length); + projectSet.addAll(Arrays.asList(projects)); + VertexFilter filter = new VertexFilter() { + @Override + public boolean matches(Object vertex) { + return !projectSet.contains(vertex); + } + }; + + // Filter the order and return it + return vertexOrderToProjectOrder(ComputeProjectOrder.filterVertexOrder(fullProjectOrder, filter)); + } + + /** + * Computes a total ordering of the given projects buildConfigs based on both static and + * dynamic project references. If an existing and open project's build configuratioin P references + * another existing and open project's configuration Q also included in the list, then Q + * should come before P in the resulting ordering. Closed and non-existent + * projects are ignored, and will not appear in the result. References to + * non-existent or closed projects/buildConfigs are also ignored, as are any + * self-references. The total ordering is always consistent with the global + * total ordering of all open projects' buildConfigs in the workspace. + *

+ * When there are choices, the choice is made in a reasonably stable way. + * For example, given an arbitrary choice between two project buildConfigs, the one with + * the lower collating configuration name is usually selected. + *

+ *

+ * When the project reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references + * P1, P4 references P3, and P2 and P3 reference each other, then exactly + * one of the relationships between P2 and P3 will have to be ignored. The + * outcome will be either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result + * also contains complete details of any cycles present. + *

+ *

+ * This method is time-consuming and should not be called unnecessarily. + * There are a very limited set of changes to a workspace that could affect + * the outcome: creating, renaming, or deleting a project; opening or + * closing a project; deleting a build configuration; adding or removing a build configuration reference. + *

+ * + * @param buildConfigs the build configurations to order + * @return result describing the build configuration order + * @since 3.7 + */ + public ProjectBuildConfigOrder computeProjectBuildConfigOrder(IBuildConfiguration[] buildConfigs) { + // Compute the full project order for all accessible projects + VertexOrder fullBuildConfigOrder = computeFullBuildConfigOrder(); + + // Create a filter to remove all project buildConfigs that are not in the list asked for + final Set projectConfigSet = new HashSet(buildConfigs.length); + projectConfigSet.addAll(Arrays.asList(buildConfigs)); + VertexFilter filter = new VertexFilter() { + @Override + public boolean matches(Object vertex) { + return !projectConfigSet.contains(vertex); + } + }; + + // Filter the order and return it + return vertexOrderToProjectBuildConfigOrder(ComputeProjectOrder.filterVertexOrder(fullBuildConfigOrder, filter)); + } + + @Override + public IStatus copy(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + return copy(resources, destination, updateFlags, monitor); + } + + @Override + public IStatus copy(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + Assert.isLegal(resources != null); + int opWork = Math.max(resources.length, 1); + int totalWork = Policy.totalWork * opWork / Policy.opWork; + String message = Messages.resources_copying_0; + monitor.beginTask(message, totalWork); + if (resources.length == 0) + return Status.OK_STATUS; + // to avoid concurrent changes to this array + resources = resources.clone(); + IPath parentPath = null; + message = Messages.resources_copyProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + try { + prepareOperation(getRoot(), monitor); + beginOperation(true); + for (int i = 0; i < resources.length; i++) { + Policy.checkCanceled(monitor); + IResource resource = resources[i]; + if (resource == null || isDuplicate(resources, i)) { + monitor.worked(1); + continue; + } + // test siblings + if (parentPath == null) + parentPath = resource.getFullPath().removeLastSegments(1); + if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) { + // test copy requirements + try { + IPath destinationPath = destination.append(resource.getName()); + IStatus requirements = ((Resource) resource).checkCopyRequirements(destinationPath, resource.getType(), updateFlags); + if (requirements.isOK()) { + try { + resource.copy(destinationPath, updateFlags, Policy.subMonitorFor(monitor, 1)); + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + status.merge(requirements); + } + } catch (CoreException e) { + monitor.worked(1); + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + message = NLS.bind(Messages.resources_notChild, resources[i].getFullPath(), parentPath); + status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resources[i].getFullPath(), message)); + } + } + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } finally { + endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork)); + } + if (status.matches(IStatus.ERROR)) + throw new ResourceException(status); + return status.isOK() ? Status.OK_STATUS : (IStatus) status; + } finally { + monitor.done(); + } + } + + protected void copyTree(IResource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo) throws CoreException { + copyTree(source, destination, depth, updateFlags, keepSyncInfo, false, source.getType() == IResource.PROJECT); + } + + private void copyTree(IResource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo, boolean moveResources, boolean movingProject) throws CoreException { + + // retrieve the resource at the destination if there is one (phantoms included). + // if there isn't one, then create a new handle based on the type that we are + // trying to copy + IResource destinationResource = getRoot().findMember(destination, true); + int destinationType; + if (destinationResource == null) { + if (source.getType() == IResource.FILE) + destinationType = IResource.FILE; + else if (destination.segmentCount() == 1) + destinationType = IResource.PROJECT; + else + destinationType = IResource.FOLDER; + destinationResource = newResource(destination, destinationType); + } else + destinationType = destinationResource.getType(); + + // create the resource at the destination + ResourceInfo sourceInfo = ((Resource) source).getResourceInfo(true, false); + if (destinationType != source.getType()) { + sourceInfo = (ResourceInfo) sourceInfo.clone(); + sourceInfo.setType(destinationType); + } + ResourceInfo newInfo = createResource(destinationResource, sourceInfo, false, true, keepSyncInfo); + // get/set the node id from the source's resource info so we can later put it in the + // info for the destination resource. This will help us generate the proper deltas, + // indicating a move rather than a add/delete + newInfo.setNodeId(sourceInfo.getNodeId()); + + // preserve local sync info but not location info + newInfo.setFlags(newInfo.getFlags() | (sourceInfo.getFlags() & M_LOCAL_EXISTS)); + newInfo.setFileStoreRoot(null); + + // forget content-related caching flags + newInfo.clear(M_CONTENT_CACHE); + + // update link locations in project descriptions + if (source.isLinked()) { + LinkDescription linkDescription; + URI sourceLocationURI = transferVariableDefinition(source, destinationResource, source.getLocationURI()); + if (((updateFlags & IResource.SHALLOW) != 0) || ((Resource) source).isUnderVirtual()) { + //for shallow move the destination is a linked resource with the same location + newInfo.set(ICoreConstants.M_LINK); + linkDescription = new LinkDescription(destinationResource, sourceLocationURI); + } else { + //for deep move the destination is not a linked resource + newInfo.clear(ICoreConstants.M_LINK); + linkDescription = null; + } + if (moveResources && !movingProject) { + if (((Project) source.getProject()).internalGetDescription().setLinkLocation(source.getProjectRelativePath(), null)) + ((Project) source.getProject()).writeDescription(updateFlags); + } + Project project = (Project) destinationResource.getProject(); + project.internalGetDescription().setLinkLocation(destinationResource.getProjectRelativePath(), linkDescription); + project.writeDescription(updateFlags); + newInfo.setFileStoreRoot(null); + } + + // update filters in project descriptions + if (source.getProject().exists() && source instanceof Container && ((Container) source).hasFilters()) { + Project sourceProject = (Project) source.getProject(); + LinkedList originalDescriptions = sourceProject.internalGetDescription().getFilter(source.getProjectRelativePath()); + LinkedList filterDescriptions = FilterDescription.copy(originalDescriptions, destinationResource); + if (moveResources && !movingProject) { + if (((Project) source.getProject()).internalGetDescription().setFilters(source.getProjectRelativePath(), null)) + ((Project) source.getProject()).writeDescription(updateFlags); + } + Project project = (Project) destinationResource.getProject(); + project.internalGetDescription().setFilters(destinationResource.getProjectRelativePath(), filterDescriptions); + project.writeDescription(updateFlags); + } + + // do the recursion. if we have a file then it has no members so return. otherwise + // recursively call this method on the container's members if the depth tells us to + if (depth == IResource.DEPTH_ZERO || source.getType() == IResource.FILE) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.DEPTH_ZERO; + //copy .project file first if project is being copied, otherwise links won't be able to update description + boolean projectCopy = source.getType() == IResource.PROJECT && destinationType == IResource.PROJECT; + if (projectCopy) { + IResource dotProject = ((Project) source).findMember(IProjectDescription.DESCRIPTION_FILE_NAME); + if (dotProject != null) + copyTree(dotProject, destination.append(dotProject.getName()), depth, updateFlags, keepSyncInfo, moveResources, movingProject); + } + IResource[] children = ((IContainer) source).members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + for (int i = 0, imax = children.length; i < imax; i++) { + String childName = children[i].getName(); + if (!projectCopy || !childName.equals(IProjectDescription.DESCRIPTION_FILE_NAME)) { + IPath childPath = destination.append(childName); + copyTree(children[i], childPath, depth, updateFlags, keepSyncInfo, moveResources, movingProject); + } + } + } + + public URI transferVariableDefinition(IResource source, IResource dest, URI sourceURI) throws CoreException { + IPath srcLoc = source.getLocation(); + IPath srcRawLoc = source.getRawLocation(); + if ((srcLoc != null) && (srcRawLoc != null) && !srcLoc.equals(srcRawLoc)) { + // the location is variable relative + if (!source.getProject().equals(dest.getProject())) { + String variable = srcRawLoc.segment(0); + variable = copyVariable(source, dest, variable); + IPath newLocation = Path.fromPortableString(variable).append(srcRawLoc.removeFirstSegments(1)); + sourceURI = toURI(newLocation); + } else { + sourceURI = toURI(srcRawLoc); + } + } + return sourceURI; + } + + URI toURI(IPath path) { + if (path.isAbsolute()) + return org.eclipse.core.filesystem.URIUtil.toURI(path); + try { + return new URI(null, null, path.toPortableString(), null); + } catch (URISyntaxException e) { + return org.eclipse.core.filesystem.URIUtil.toURI(path); + } + } + + String copyVariable(IResource source, IResource dest, String variable) throws CoreException { + IPathVariableManager destPathVariableManager = dest.getPathVariableManager(); + IPathVariableManager srcPathVariableManager = source.getPathVariableManager(); + + IPath srcValue = URIUtil.toPath(srcPathVariableManager.getURIValue(variable)); + if (srcValue == null) // if the variable doesn't exist, return another + // variable that doesn't exist either + return PathVariableUtil.getUniqueVariableName(variable, dest); + IPath resolvedSrcValue = URIUtil.toPath(srcPathVariableManager.resolveURI(URIUtil.toURI(srcValue))); + + boolean variableExisted = false; + // look if the exact same variable exists + if (destPathVariableManager.isDefined(variable)) { + variableExisted = true; + IPath destValue = URIUtil.toPath(destPathVariableManager.getURIValue(variable)); + if (destValue != null && URIUtil.toPath(destPathVariableManager.resolveURI(URIUtil.toURI(destValue))).equals(resolvedSrcValue)) + return variable; + } + // look if one variable in the destination project matches + String[] variables = destPathVariableManager.getPathVariableNames(); + for (int i = 0; i < variables.length; i++) { + if (!PathVariableUtil.isPreferred(variables[i])) + continue; + IPath resolveDestVariable = URIUtil.toPath(destPathVariableManager.resolveURI(destPathVariableManager.getURIValue(variables[i]))); + if (resolveDestVariable != null && resolveDestVariable.equals(resolvedSrcValue)) { + return variables[i]; + } + } + // if the variable doesn't exist in the dest project, or + // if the value is different than the source project, we have to create + // an equivalent. + String destVariable = PathVariableUtil.getUniqueVariableName(variable, dest); + + boolean shouldConvertToRelative = true; + if (!srcValue.equals(resolvedSrcValue) && !variableExisted) { + // the variable content contains references to more variables + + String[] referencedVariables = PathVariableUtil.splitVariableNames(srcValue.toPortableString()); + shouldConvertToRelative = false; + // If the variable value is of type ${PARENT-COUNT-VAR}, + // we can avoid generating an intermediate variable and convert it directly. + if (referencedVariables.length == 1) { + if (PathVariableUtil.isParentVariable(referencedVariables[0])) + shouldConvertToRelative = true; + } + + if (!shouldConvertToRelative) { + String[] segments = PathVariableUtil.splitVariablesAndContent(srcValue.toPortableString()); + StringBuffer result = new StringBuffer(); + for (int i = 0; i < segments.length; i++) { + String var = PathVariableUtil.extractVariable(segments[i]); + if (var.length() > 0) { + String copiedVariable = copyVariable(source, dest, var); + int index = segments[i].indexOf(var); + if (index != -1) { + result.append(segments[i].substring(0, index)); + result.append(copiedVariable); + int start = index + var.length(); + int end = segments[i].length(); + result.append(segments[i].substring(start, end)); + } + } else + result.append(segments[i]); + } + srcValue = Path.fromPortableString(result.toString()); + } + } + if (shouldConvertToRelative) { + IPath relativeSrcValue = PathVariableUtil.convertToPathRelativeMacro(destPathVariableManager, resolvedSrcValue, dest, true, null); + if (relativeSrcValue != null) + srcValue = relativeSrcValue; + } + destPathVariableManager.setURIValue(destVariable, URIUtil.toURI(srcValue)); + return destVariable; + } + + /** + * Returns the number of resources in a subtree of the resource tree. + * + * @param root The subtree to count resources for + * @param depth The depth of the subtree to count + * @param phantom If true, phantoms are included, otherwise they are ignored. + */ + public int countResources(IPath root, int depth, final boolean phantom) { + if (!tree.includes(root)) + return 0; + switch (depth) { + case IResource.DEPTH_ZERO : + return 1; + case IResource.DEPTH_ONE : + return 1 + tree.getChildCount(root); + case IResource.DEPTH_INFINITE : + final int[] count = new int[1]; + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree aTree, IPathRequestor requestor, Object elementContents) { + if (phantom || !((ResourceInfo) elementContents).isSet(M_PHANTOM)) + count[0]++; + return true; + } + }; + new ElementTreeIterator(tree, root).iterate(visitor); + return count[0]; + } + return 0; + } + + /* + * Creates the given resource in the tree and returns the new resource info object. + * If phantom is true, the created element is marked as a phantom. + * If there is already be an element in the tree for the given resource + * in the given state (i.e., phantom), a CoreException is thrown. + * If there is already a phantom in the tree and the phantom flag is false, + * the element is overwritten with the new element. (but the synchronization + * information is preserved) + */ + public ResourceInfo createResource(IResource resource, boolean phantom) throws CoreException { + return createResource(resource, null, phantom, false, false); + } + + /** + * Creates a resource, honoring update flags requesting that the resource + * be immediately made derived, hidden and/or team private + */ + public ResourceInfo createResource(IResource resource, int updateFlags) throws CoreException { + ResourceInfo info = createResource(resource, null, false, false, false); + if ((updateFlags & IResource.DERIVED) != 0) + info.set(M_DERIVED); + if ((updateFlags & IResource.TEAM_PRIVATE) != 0) + info.set(M_TEAM_PRIVATE_MEMBER); + if ((updateFlags & IResource.HIDDEN) != 0) + info.set(M_HIDDEN); + // if ((updateFlags & IResource.VIRTUAL) != 0) + // info.set(M_VIRTUAL); + return info; + } + + /* + * Creates the given resource in the tree and returns the new resource info object. + * If phantom is true, the created element is marked as a phantom. + * If there is already be an element in the tree for the given resource + * in the given state (i.e., phantom), a CoreException is thrown. + * If there is already a phantom in the tree and the phantom flag is false, + * the element is overwritten with the new element. (but the synchronization + * information is preserved) If the specified resource info is null, then create + * a new one. + * + * If keepSyncInfo is set to be true, the sync info in the given ResourceInfo is NOT + * cleared before being created and thus any sync info already existing at that namespace + * (as indicated by an already existing phantom resource) will be lost. + */ + public ResourceInfo createResource(IResource resource, ResourceInfo info, boolean phantom, boolean overwrite, boolean keepSyncInfo) throws CoreException { + info = info == null ? newElement(resource.getType()) : (ResourceInfo) info.clone(); + ResourceInfo original = getResourceInfo(resource.getFullPath(), true, false); + if (phantom) { + info.set(M_PHANTOM); + info.clearModificationStamp(); + } + // if nothing existed at the destination then just create the resource in the tree + if (original == null) { + // we got here from a copy/move. we don't want to copy over any sync info + // from the source so clear it. + if (!keepSyncInfo) + info.setSyncInfo(null); + tree.createElement(resource.getFullPath(), info); + } else { + // if overwrite==true then slam the new info into the tree even if one existed before + if (overwrite || (!phantom && original.isSet(M_PHANTOM))) { + // copy over the sync info and flags from the old resource info + // since we are replacing a phantom with a real resource + // DO NOT set the sync info dirty flag because we want to + // preserve the old sync info so its not dirty + // XXX: must copy over the generic sync info from the old info to the new + // XXX: do we really need to clone the sync info here? + if (!keepSyncInfo) + info.setSyncInfo(original.getSyncInfo(true)); + // mark the markers bit as dirty so we snapshot an empty marker set for + // the new resource + info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY); + tree.setElementData(resource.getFullPath(), info); + } else { + String message = NLS.bind(Messages.resources_mustNotExist, resource.getFullPath()); + throw new ResourceException(IResourceStatus.RESOURCE_EXISTS, resource.getFullPath(), message, null); + } + } + return info; + } + + @Override + public IStatus delete(IResource[] resources, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= IResource.KEEP_HISTORY; + return delete(resources, updateFlags, monitor); + } + + @Override + public IStatus delete(IResource[] resources, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + int opWork = Math.max(resources.length, 1); + int totalWork = Policy.totalWork * opWork / Policy.opWork; + String message = Messages.resources_deleting_0; + monitor.beginTask(message, totalWork); + message = Messages.resources_deleteProblem; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + if (resources.length == 0) + return result; + resources = resources.clone(); // to avoid concurrent changes to this array + try { + prepareOperation(getRoot(), monitor); + beginOperation(true); + for (int i = 0; i < resources.length; i++) { + Policy.checkCanceled(monitor); + Resource resource = (Resource) resources[i]; + if (resource == null) { + monitor.worked(1); + continue; + } + try { + resource.delete(updateFlags, Policy.subMonitorFor(monitor, 1)); + } catch (CoreException e) { + // Don't really care about the exception unless the resource is still around. + ResourceInfo info = resource.getResourceInfo(false, false); + if (resource.exists(resource.getFlags(info), false)) { + message = NLS.bind(Messages.resources_couldnotDelete, resource.getFullPath()); + result.merge(new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, resource.getFullPath(), message)); + result.merge(e.getStatus()); + } + } + } + if (result.matches(IStatus.ERROR)) + throw new ResourceException(result); + return result; + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } finally { + endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void deleteMarkers(IMarker[] markers) throws CoreException { + Assert.isNotNull(markers); + if (markers.length == 0) + return; + // clone to avoid outside changes + markers = markers.clone(); + try { + prepareOperation(null, null); + beginOperation(true); + for (int i = 0; i < markers.length; ++i) + if (markers[i] != null && markers[i].getResource() != null) + markerManager.removeMarker(markers[i].getResource(), markers[i].getId()); + } finally { + endOperation(null, false, null); + } + } + + /** + * Delete the given resource from the current tree of the receiver. + * This method simply removes the resource from the tree. No cleanup or + * other management is done. Use IResource.delete for proper deletion. + * If the given resource is the root, all of its children (i.e., all projects) are + * deleted but the root is left. + */ + void deleteResource(IResource resource) { + IPath path = resource.getFullPath(); + if (path.equals(Path.ROOT)) { + IProject[] children = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < children.length; i++) + tree.deleteElement(children[i].getFullPath()); + } else + tree.deleteElement(path); + } + + /** + * End an operation (group of resource changes). + * Notify interested parties that resource changes have taken place. All + * registered resource change listeners are notified. If autobuilding is + * enabled, a build is run. + */ + public void endOperation(ISchedulingRule rule, boolean build, IProgressMonitor monitor) throws CoreException { + WorkManager workManager = getWorkManager(); + //don't do any end operation work if we failed to check in + if (workManager.checkInFailed(rule)) + return; + // This is done in a try finally to ensure that we always decrement the operation count + // and release the workspace lock. This must be done at the end because snapshot + // and "hasChanges" comparison have to happen without interference from other threads. + boolean hasTreeChanges = false; + boolean depthOne = false; + try { + workManager.setBuild(build); + // if we are not exiting a top level operation then just decrement the count and return + depthOne = workManager.getPreparedOperationDepth() == 1; + if (!(notificationManager.shouldNotify() || depthOne)) { + notificationManager.requestNotify(); + return; + } + // do the following in a try/finally to ensure that the operation tree is nulled at the end + // as we are completing a top level operation. + try { + notificationManager.beginNotify(); + // check for a programming error on using beginOperation/endOperation + Assert.isTrue(workManager.getPreparedOperationDepth() > 0, "Mismatched begin/endOperation"); //$NON-NLS-1$ + + // At this time we need to re-balance the nested operations. It is necessary because + // build() and snapshot() should not fail if they are called. + workManager.rebalanceNestedOperations(); + + //find out if any operation has potentially modified the tree + hasTreeChanges = workManager.shouldBuild(); + //double check if the tree has actually changed + if (hasTreeChanges) + hasTreeChanges = operationTree != null && ElementTree.hasChanges(tree, operationTree, ResourceComparator.getBuildComparator(), true); + broadcastPostChange(); + // Request a snapshot if we are sufficiently out of date. + saveManager.snapshotIfNeeded(hasTreeChanges); + } finally { + // make sure the tree is immutable if we are ending a top-level operation. + if (depthOne) { + tree.immutable(); + operationTree = null; + } else + newWorkingTree(); + } + } finally { + workManager.checkOut(rule); + } + if (depthOne) + buildManager.endTopLevel(hasTreeChanges); + } + + /** + * Flush the build order cache for the workspace. The buildOrder cache contains the total + * order of the build configurations in the workspace, including projects not mentioned in + * the workspace description. + */ + protected void flushBuildOrder() { + buildOrder = null; + } + + @Override + public void forgetSavedTree(String pluginId) { + saveManager.forgetSavedTree(pluginId); + } + + public AliasManager getAliasManager() { + return aliasManager; + } + + /** + * Returns this workspace's build manager + */ + public BuildManager getBuildManager() { + return buildManager; + } + + /** + * Returns the order in which open projects in this workspace will be built. + * The result returned is a list of project buildConfigs, that need to be built + * in order to successfully build the active config of every project in this + * workspace. + *

+ * The build configuration order is based on information specified in the workspace + * description. The project build configs are built in the order specified by + * IWorkspaceDescription.getBuildOrder; closed or non-existent + * projects are ignored and not included in the result. If any open projects are + * not specified in this order, they are appended to the end of the build order + * sorted by project name (to provide a stable ordering). + *

+ *

+ * If IWorkspaceDescription.getBuildOrder is non-null, the default + * build order is used (calculated based on references); again, only open projects' + * buildConfigs are included in the result. + *

+ *

+ * The returned value is cached in the buildOrder field. + *

+ * + * @return the list of currently open projects active buildConfigs (and the project buildConfigs + * they depend on) in the workspace in the order in which they would be built by IWorkspace.build. + * @see IWorkspace#build(int, IProgressMonitor) + * @see IWorkspaceDescription#getBuildOrder() + */ + public IBuildConfiguration[] getBuildOrder() { + // Return the build order cache. + if (buildOrder != null) + return buildOrder; + + // see if a particular build order is specified + String[] order = description.getBuildOrder(false); + if (order != null) { + LinkedHashSet configs = new LinkedHashSet(); + + // convert from project names to active project buildConfigs + // and eliminate non-existent and closed projects + for (int i = 0; i < order.length; i++) { + IProject project = getRoot().getProject(order[i]); + if (project.isAccessible()) + configs.add(((Project) project).internalGetActiveBuildConfig()); + } + + // Add projects not mentioned in the build order to the end, in a sensible reference order + configs.addAll(Arrays.asList(vertexOrderToProjectBuildConfigOrder(computeActiveBuildConfigOrder()).buildConfigurations)); + + // Update the cache - Java 5 volatile memory barrier semantics + IBuildConfiguration[] bo = new IBuildConfiguration[configs.size()]; + configs.toArray(bo); + this.buildOrder = bo; + } else + // use default project build order + // computed for all accessible projects in workspace + buildOrder = vertexOrderToProjectBuildConfigOrder(computeActiveBuildConfigOrder()).buildConfigurations; + + return buildOrder; + } + + public CharsetManager getCharsetManager() { + return charsetManager; + } + + public ContentDescriptionManager getContentDescriptionManager() { + return contentDescriptionManager; + } + + @Override + public Map getDanglingReferences() { + IProject[] projects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + Map result = new HashMap(projects.length); + for (int i = 0; i < projects.length; i++) { + Project project = (Project) projects[i]; + if (!project.isAccessible()) + continue; + IProject[] refs = project.internalGetDescription().getReferencedProjects(false); + List dangling = new ArrayList(refs.length); + for (int j = 0; j < refs.length; j++) + if (!refs[i].exists()) + dangling.add(refs[i]); + if (!dangling.isEmpty()) + result.put(projects[i], dangling.toArray(new IProject[dangling.size()])); + } + return result; + } + + @Override + public IWorkspaceDescription getDescription() { + WorkspaceDescription workingCopy = defaultWorkspaceDescription(); + description.copyTo(workingCopy); + return workingCopy; + } + + /** + * Returns the current element tree for this workspace + */ + public ElementTree getElementTree() { + return tree; + } + + public FileSystemResourceManager getFileSystemManager() { + return fileSystemManager; + } + + /** + * Returns the marker manager for this workspace + */ + public MarkerManager getMarkerManager() { + return markerManager; + } + + public LocalMetaArea getMetaArea() { + return localMetaArea; + } + + protected IMoveDeleteHook getMoveDeleteHook() { + if (moveDeleteHook == null) + initializeMoveDeleteHook(); + return moveDeleteHook; + } + + @Override + public IFilterMatcherDescriptor getFilterMatcherDescriptor(String filterMatcherId) { + return filterManager.getFilterDescriptor(filterMatcherId); + } + + @Override + public IFilterMatcherDescriptor[] getFilterMatcherDescriptors() { + return filterManager.getFilterDescriptors(); + } + + @Override + public IProjectNatureDescriptor getNatureDescriptor(String natureId) { + return natureManager.getNatureDescriptor(natureId); + } + + @Override + public IProjectNatureDescriptor[] getNatureDescriptors() { + return natureManager.getNatureDescriptors(); + } + + /** + * Returns the nature manager for this workspace. + */ + public NatureManager getNatureManager() { + return natureManager; + } + + public NotificationManager getNotificationManager() { + return notificationManager; + } + + @Override + public IPathVariableManager getPathVariableManager() { + return pathVariableManager; + } + + public IPropertyManager getPropertyManager() { + return propertyManager; + } + + /** + * Returns the refresh manager for this workspace + */ + public RefreshManager getRefreshManager() { + return refreshManager; + } + + /** + * Returns the resource info for the identified resource. + * null is returned if no such resource can be found. + * If the phantom flag is true, phantom resources are considered. + * If the mutable flag is true, the info is opened for change. + * + * This method DOES NOT throw an exception if the resource is not found. + */ + public ResourceInfo getResourceInfo(IPath path, boolean phantom, boolean mutable) { + try { + if (path.segmentCount() == 0) { + ResourceInfo info = (ResourceInfo) tree.getTreeData(); + Assert.isNotNull(info, "Tree root info must never be null"); //$NON-NLS-1$ + return info; + } + ResourceInfo result = null; + if (!tree.includes(path)) + return null; + if (mutable) + result = (ResourceInfo) tree.openElementData(path); + else + result = (ResourceInfo) tree.getElementData(path); + if (result != null && (!phantom && result.isSet(M_PHANTOM))) + return null; + return result; + } catch (IllegalArgumentException e) { + return null; + } + } + + @Override + public IWorkspaceRoot getRoot() { + return defaultRoot; + } + + @Override + public IResourceRuleFactory getRuleFactory() { + //note that the rule factory is created lazily because it + //requires loading the teamHook extension + if (ruleFactory == null) + ruleFactory = new Rules(this); + return ruleFactory; + } + + public SaveManager getSaveManager() { + return saveManager; + } + + @Override + public ISynchronizer getSynchronizer() { + return synchronizer; + } + + /** + * Returns the installed team hook. Never returns null. + */ + protected TeamHook getTeamHook() { + if (teamHook == null) + initializeTeamHook(); + return teamHook; + } + + /** + * We should not have direct references to this field. All references should go through + * this method. + */ + public WorkManager getWorkManager() throws CoreException { + if (_workManager == null) { + String message = Messages.resources_shutdown; + throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, null, message)); + } + return _workManager; + } + + /** + * A move/delete hook hasn't been initialized. Check the extension point and + * try to create a new hook if a user has one defined as an extension. Otherwise + * use the Core's implementation as the default. + */ + protected void initializeMoveDeleteHook() { + try { + if (!canCreateExtensions()) + return; + IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MOVE_DELETE_HOOK); + // no-one is plugged into the extension point so disable validation + if (configs == null || configs.length == 0) { + return; + } + // can only have one defined at a time. log a warning + if (configs.length > 1) { + //XXX: should provide a meaningful status code + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneHook, null); + Policy.log(status); + return; + } + // otherwise we have exactly one hook extension. Try to create a new instance + // from the user-specified class. + try { + IConfigurationElement config = configs[0]; + moveDeleteHook = (IMoveDeleteHook) config.createExecutableExtension("class"); //$NON-NLS-1$ + } catch (CoreException e) { + //ignore the failure if we are shutting down (expected since extension + //provider plugin has probably already shut down + if (canCreateExtensions()) { + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initHook, e); + Policy.log(status); + } + } + } finally { + // for now just use Core's implementation + if (moveDeleteHook == null) + moveDeleteHook = new MoveDeleteHook(); + } + } + + /** + * A team hook hasn't been initialized. Check the extension point and + * try to create a new hook if a user has one defined as an extension. + * Otherwise use the Core's implementation as the default. + */ + protected void initializeTeamHook() { + try { + if (!canCreateExtensions()) + return; + IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_TEAM_HOOK); + // no-one is plugged into the extension point so disable validation + if (configs == null || configs.length == 0) { + return; + } + // can only have one defined at a time. log a warning + if (configs.length > 1) { + //XXX: should provide a meaningful status code + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneTeamHook, null); + Policy.log(status); + return; + } + // otherwise we have exactly one hook extension. Try to create a new instance + // from the user-specified class. + try { + IConfigurationElement config = configs[0]; + teamHook = (TeamHook) config.createExecutableExtension("class"); //$NON-NLS-1$ + } catch (CoreException e) { + //ignore the failure if we are shutting down (expected since extension + //provider plugin has probably already shut down + if (canCreateExtensions()) { + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initTeamHook, e); + Policy.log(status); + } + } + } finally { + // default to use Core's implementation + //create anonymous subclass because TeamHook is abstract + if (teamHook == null) + teamHook = new TeamHook() { + // empty + }; + } + } + + /** + * A file modification validator hasn't been initialized. Check the extension point and + * try to create a new validator if a user has one defined as an extension. + */ + protected void initializeValidator() { + shouldValidate = false; + if (!canCreateExtensions()) + return; + IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_FILE_MODIFICATION_VALIDATOR); + // no-one is plugged into the extension point so disable validation + if (configs == null || configs.length == 0) { + return; + } + // can only have one defined at a time. log a warning, disable validation, but continue with + // the #setContents (e.g. don't throw an exception) + if (configs.length > 1) { + //XXX: should provide a meaningful status code + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneValidator, null); + Policy.log(status); + return; + } + // otherwise we have exactly one validator extension. Try to create a new instance + // from the user-specified class. + try { + IConfigurationElement config = configs[0]; + validator = (IFileModificationValidator) config.createExecutableExtension("class"); //$NON-NLS-1$ + shouldValidate = true; + } catch (CoreException e) { + //ignore the failure if we are shutting down (expected since extension + //provider plugin has probably already shut down + if (canCreateExtensions()) { + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initValidator, e); + Policy.log(status); + } + } + } + + public WorkspaceDescription internalGetDescription() { + return description; + } + + @Override + public boolean isAutoBuilding() { + return description.isAutoBuilding(); + } + + public boolean isOpen() { + return openFlag; + } + + @Override + public boolean isTreeLocked() { + return treeLocked == Thread.currentThread(); + } + + /** + * Link the given tree into the receiver's tree at the specified resource. + */ + protected void linkTrees(IPath path, ElementTree[] newTrees) { + tree = tree.mergeDeltaChain(path, newTrees); + } + + @Override + public IProjectDescription loadProjectDescription(InputStream stream) throws CoreException { + IProjectDescription result = null; + result = new ProjectDescriptionReader().read(new InputSource(stream)); + if (result == null) { + String message = NLS.bind(Messages.resources_errorReadProject, stream.toString()); + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message, null); + throw new ResourceException(status); + } + return result; + } + + @Override + public IProjectDescription loadProjectDescription(IPath path) throws CoreException { + IProjectDescription result = null; + IOException e = null; + try { + result = new ProjectDescriptionReader().read(path); + if (result != null) { + // check to see if we are using in the default area or not. use java.io.File for + // testing equality because it knows better w.r.t. drives and case sensitivity + IPath user = path.removeLastSegments(1); + IPath platform = getRoot().getLocation().append(result.getName()); + if (!user.toFile().equals(platform.toFile())) + result.setLocation(user); + } + } catch (IOException ex) { + e = ex; + } + if (result == null || e != null) { + String message = NLS.bind(Messages.resources_errorReadProject, path.toOSString()); + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message, e); + throw new ResourceException(status); + } + return result; + } + + @Override + public IStatus move(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= IResource.KEEP_HISTORY; + return move(resources, destination, updateFlags, monitor); + } + + @Override + public IStatus move(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + Assert.isLegal(resources != null); + int opWork = Math.max(resources.length, 1); + int totalWork = Policy.totalWork * opWork / Policy.opWork; + String message = Messages.resources_moving_0; + monitor.beginTask(message, totalWork); + if (resources.length == 0) + return Status.OK_STATUS; + resources = resources.clone(); // to avoid concurrent changes to this array + IPath parentPath = null; + message = Messages.resources_moveProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + try { + prepareOperation(getRoot(), monitor); + beginOperation(true); + for (int i = 0; i < resources.length; i++) { + Policy.checkCanceled(monitor); + Resource resource = (Resource) resources[i]; + if (resource == null || isDuplicate(resources, i)) { + monitor.worked(1); + continue; + } + // test siblings + if (parentPath == null) + parentPath = resource.getFullPath().removeLastSegments(1); + if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) { + // test move requirements + try { + IStatus requirements = resource.checkMoveRequirements(destination.append(resource.getName()), resource.getType(), updateFlags); + if (requirements.isOK()) { + try { + resource.move(destination.append(resource.getName()), updateFlags, Policy.subMonitorFor(monitor, 1)); + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + status.merge(requirements); + } + } catch (CoreException e) { + monitor.worked(1); + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + message = NLS.bind(Messages.resources_notChild, resource.getFullPath(), parentPath); + status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resource.getFullPath(), message)); + } + } + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } finally { + endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork)); + } + if (status.matches(IStatus.ERROR)) + throw new ResourceException(status); + return status.isOK() ? (IStatus) Status.OK_STATUS : (IStatus) status; + } finally { + monitor.done(); + } + } + + /** + * Moves this resource's subtree to the destination. This operation should only be + * used by move methods. Destination must be a valid destination for this resource. + * The keepSyncInfo boolean is used to indicated whether or not the sync info should + * be moved from the source to the destination. + */ + + /* package */ + void move(Resource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo) throws CoreException { + // overlay the tree at the destination path, preserving any important info + // in any already existing resource information + copyTree(source, destination, depth, updateFlags, keepSyncInfo, true, source.getType() == IResource.PROJECT); + source.fixupAfterMoveSource(); + } + + /** + * Create and return a new tree element of the given type. + */ + protected ResourceInfo newElement(int type) { + ResourceInfo result = null; + switch (type) { + case IResource.FILE : + case IResource.FOLDER : + result = new ResourceInfo(); + break; + case IResource.PROJECT : + result = new ProjectInfo(); + break; + case IResource.ROOT : + result = new RootInfo(); + break; + } + result.setNodeId(nextNodeId()); + updateModificationStamp(result); + result.setType(type); + return result; + } + + @Override + public IBuildConfiguration newBuildConfig(String projectName, String configName) { + return new BuildConfiguration(getRoot().getProject(projectName), configName); + } + + @Override + public IProjectDescription newProjectDescription(String projectName) { + IProjectDescription result = new ProjectDescription(); + result.setName(projectName); + return result; + } + + public Resource newResource(IPath path, int type) { + String message; + switch (type) { + case IResource.FOLDER : + if (path.segmentCount() < ICoreConstants.MINIMUM_FOLDER_SEGMENT_LENGTH) { + message = "Path must include project and resource name: " + path.toString(); //$NON-NLS-1$ + Assert.isLegal(false, message); + } + return new Folder(path.makeAbsolute(), this); + case IResource.FILE : + if (path.segmentCount() < ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH) { + message = "Path must include project and resource name: " + path.toString(); //$NON-NLS-1$ + Assert.isLegal(false, message); + } + return new File(path.makeAbsolute(), this); + case IResource.PROJECT : + return (Resource) getRoot().getProject(path.lastSegment()); + case IResource.ROOT : + return (Resource) getRoot(); + } + Assert.isLegal(false); + // will never get here because of assertion. + return null; + } + + /** + * Opens a new mutable element tree layer, thus allowing + * modifications to the tree. + */ + public ElementTree newWorkingTree() { + tree = tree.newEmptyDelta(); + return tree; + } + + /** + * Returns the next, previously unassigned, marker id. + */ + protected long nextMarkerId() { + return nextMarkerId++; + } + + protected long nextNodeId() { + return nextNodeId++; + } + + /** + * Opens this workspace using the data at its location in the local file system. + * This workspace must not be open. + * If the operation succeeds, the result will detail any serious + * (but non-fatal) problems encountered while opening the workspace. + * The status code will be OK if there were no problems. + * An exception is thrown if there are fatal problems opening the workspace, + * in which case the workspace is left closed. + *

+ * 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 and cancellation are not desired + * @return status with code OK if no problems; + * otherwise status describing any serious but non-fatal problems. + * + * @exception CoreException if the workspace could not be opened. + * Reasons include: + *
    + *
  • There is no valid workspace structure at the given location + * in the local file system.
  • + *
  • The workspace structure on disk appears to be hopelessly corrupt.
  • + *
+ * @see ResourcesPlugin#getWorkspace() + */ + public IStatus open(IProgressMonitor monitor) throws CoreException { + // This method is not inside an operation because it is the one responsible for + // creating the WorkManager object (who takes care of operations). + String message = Messages.resources_workspaceOpen; + Assert.isTrue(!isOpen(), message); + if (!getMetaArea().hasSavedWorkspace()) { + message = Messages.resources_readWorkspaceMeta; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, Platform.getLocation(), message, null); + } + description = new WorkspacePreferences(); + + // if we have an old description file, read it (getting rid of it) + WorkspaceDescription oldDescription = getMetaArea().readOldWorkspace(); + if (oldDescription != null) { + description.copyFrom(oldDescription); + ResourcesPlugin.getPlugin().savePluginPreferences(); + } + + // create root location + localMetaArea.locationFor(getRoot()).toFile().mkdirs(); + + IProgressMonitor nullMonitor = Policy.monitorFor(null); + startup(nullMonitor); + //restart the notification manager so it is initialized with the right tree + notificationManager.startup(null); + openFlag = true; + if (crashed || refreshRequested()) { + try { + refreshManager.refresh(getRoot()); + } catch (RuntimeException e) { + //don't fail entire open if refresh failed, just report as warning + return new ResourceStatus(IResourceStatus.INTERNAL_ERROR, Path.ROOT, Messages.resources_errorMultiRefresh, e); + } + } + //finally register a string pool participant + stringPoolJob = new StringPoolJob(); + stringPoolJob.addStringPoolParticipant(saveManager, getRoot()); + return Status.OK_STATUS; + } + + /** + * Called before checking the pre-conditions of an operation. Optionally supply + * a scheduling rule to determine when the operation is safe to run. If a scheduling + * rule is supplied, this method will block until it is safe to run. + * + * @param rule the scheduling rule that describes what this operation intends to modify. + */ + public void prepareOperation(ISchedulingRule rule, IProgressMonitor monitor) throws CoreException { + try { + //make sure autobuild is not running if it conflicts with this operation + ISchedulingRule buildRule = getRuleFactory().buildRule(); + if (rule != null && buildRule != null && (rule.isConflicting(buildRule) || buildRule.isConflicting(rule))) + buildManager.interrupt(); + } finally { + getWorkManager().checkIn(rule, monitor); + } + if (!isOpen()) { + String message = Messages.resources_workspaceClosed; + throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, null); + } + } + + protected boolean refreshRequested() { + String[] args = Platform.getCommandLineArgs(); + for (int i = 0; i < args.length; i++) + if (args[i].equalsIgnoreCase(REFRESH_ON_STARTUP)) + return true; + return false; + } + + @Override + public void removeResourceChangeListener(IResourceChangeListener listener) { + notificationManager.removeListener(listener); + } + + @Deprecated + @Override + public void removeSaveParticipant(Plugin plugin) { + Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$ + saveManager.removeParticipant(plugin.getBundle().getSymbolicName()); + } + + @Override + public void removeSaveParticipant(String pluginId) { + Assert.isNotNull(pluginId, "Plugin id must not be null"); //$NON-NLS-1$ + saveManager.removeParticipant(pluginId); + } + + @Override + public void run(IWorkspaceRunnable action, IProgressMonitor monitor) throws CoreException { + run(action, defaultRoot, IWorkspace.AVOID_UPDATE, monitor); + } + + @Override + public void run(IWorkspaceRunnable action, ISchedulingRule rule, int options, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + int depth = -1; + boolean avoidNotification = (options & IWorkspace.AVOID_UPDATE) != 0; + try { + prepareOperation(rule, monitor); + beginOperation(true); + if (avoidNotification) + avoidNotification = notificationManager.beginAvoidNotify(); + depth = getWorkManager().beginUnprotected(); + action.run(Policy.subMonitorFor(monitor, Policy.opWork, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK)); + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } finally { + if (avoidNotification) + notificationManager.endAvoidNotify(); + if (depth >= 0) + getWorkManager().endUnprotected(depth); + endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public IStatus save(boolean full, IProgressMonitor monitor) throws CoreException { + return this.save(full, false, monitor); + } + + public IStatus save(boolean full, boolean keepConsistencyWhenCanceled, IProgressMonitor monitor) throws CoreException { + String message; + if (full) { + //according to spec it is illegal to start a full save inside another operation + if (getWorkManager().isLockAlreadyAcquired()) { + message = Messages.resources_saveOp; + throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, new IllegalStateException()); + } + return saveManager.save(ISaveContext.FULL_SAVE, keepConsistencyWhenCanceled, null, monitor); + } + // A snapshot was requested. Start an operation (if not already started) and + // signal that a snapshot should be done at the end. + try { + prepareOperation(getRoot(), monitor); + beginOperation(false); + saveManager.requestSnapshot(); + message = Messages.resources_snapRequest; + return new ResourceStatus(IStatus.OK, message); + } finally { + endOperation(getRoot(), false, null); + } + } + + public void setCrashed(boolean value) { + crashed = value; + if (crashed) { + String msg = "The workspace exited with unsaved changes in the previous session; refreshing workspace to recover changes."; //$NON-NLS-1$ + Policy.log(new ResourceStatus(ICoreConstants.CRASH_DETECTED, msg)); + if (Policy.DEBUG) + Policy.debug(msg); + } + } + + @Override + public void setDescription(IWorkspaceDescription value) { + // if both the old and new description's build orders are null, leave the + // workspace's build order slot because it is caching the computed order. + // Otherwise, set the slot to null to force recomputing or building from the description. + WorkspaceDescription newDescription = (WorkspaceDescription) value; + String[] newOrder = newDescription.getBuildOrder(false); + if (description.getBuildOrder(false) != null || newOrder != null) + buildOrder = null; + description.copyFrom(newDescription); + ResourcesPlugin.getPlugin().savePluginPreferences(); + } + + public void setTreeLocked(boolean locked) { + Assert.isTrue(!locked || treeLocked == null, "The workspace tree is already locked"); //$NON-NLS-1$ + treeLocked = locked ? Thread.currentThread() : null; + } + + /** + * Shuts down the workspace managers. + */ + protected void shutdown(IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + IManager[] managers = {buildManager, propertyManager, pathVariableManager, charsetManager, fileSystemManager, markerManager, _workManager, aliasManager, refreshManager, contentDescriptionManager, natureManager, filterManager}; + monitor.beginTask("", managers.length); //$NON-NLS-1$ + String message = Messages.resources_shutdownProblems; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + // best effort to shutdown every object and free resources + for (int i = 0; i < managers.length; i++) { + IManager manager = managers[i]; + if (manager == null) + monitor.worked(1); + else { + try { + manager.shutdown(Policy.subMonitorFor(monitor, 1)); + } catch (Exception e) { + message = Messages.resources_shutdownProblems; + status.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, e)); + } + } + } + buildManager = null; + notificationManager = null; + propertyManager = null; + pathVariableManager = null; + fileSystemManager = null; + markerManager = null; + synchronizer = null; + saveManager = null; + _workManager = null; + aliasManager = null; + refreshManager = null; + charsetManager = null; + contentDescriptionManager = null; + if (!status.isOK()) + throw new CoreException(status); + } finally { + monitor.done(); + } + } + + @Override + public String[] sortNatureSet(String[] natureIds) { + return natureManager.sortNatureSet(natureIds); + } + + /** + * Starts all the workspace manager classes. + */ + protected void startup(IProgressMonitor monitor) throws CoreException { + // ensure the tree is locked during the startup notification + try { + _workManager = new WorkManager(this); + _workManager.startup(null); + fileSystemManager = new FileSystemResourceManager(this); + fileSystemManager.startup(monitor); + pathVariableManager = new PathVariableManager(); + pathVariableManager.startup(null); + natureManager = new NatureManager(); + natureManager.startup(null); + filterManager = new FilterTypeManager(); + filterManager.startup(null); + buildManager = new BuildManager(this, getWorkManager().getLock()); + buildManager.startup(null); + notificationManager = new NotificationManager(this); + notificationManager.startup(null); + markerManager = new MarkerManager(this); + markerManager.startup(null); + synchronizer = new Synchronizer(this); + saveManager = new SaveManager(this); + saveManager.startup(null); + propertyManager = new PropertyManager2((Workspace) ResourcesPlugin.getWorkspace()); + propertyManager.startup(monitor); + charsetManager = new CharsetManager(this); + charsetManager.startup(null); + contentDescriptionManager = new ContentDescriptionManager(); + contentDescriptionManager.startup(null); + //must start after save manager, because (read) access to tree is needed + //must start after other managers to avoid potential cyclic dependency on uninitialized managers (see bug 316182) + //must start before alias manager (see bug 94829) + refreshManager = new RefreshManager(this); + refreshManager.startup(null); + //must start at the end to avoid potential cyclic dependency on other uninitialized managers (see bug 369177) + aliasManager = new AliasManager(this); + aliasManager.startup(null); + } finally { + //unlock tree even in case of failure, otherwise shutdown will also fail + treeLocked = null; + _workManager.postWorkspaceStartup(); + } + } + + /** + * Returns a string representation of this working state's + * structure suitable for debug purposes. + */ + public String toDebugString() { + final StringBuffer buffer = new StringBuffer("\nDump of " + toString() + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$ + buffer.append(" parent: " + tree.getParent()); //$NON-NLS-1$ + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree aTree, IPathRequestor requestor, Object elementContents) { + buffer.append("\n " + requestor.requestPath() + ": " + elementContents); //$NON-NLS-1$ //$NON-NLS-2$ + return true; + } + }; + new ElementTreeIterator(tree, Path.ROOT).iterate(visitor); + return buffer.toString(); + } + + public void updateModificationStamp(ResourceInfo info) { + info.incrementModificationStamp(); + } + + @Override + public IStatus validateEdit(final IFile[] files, final Object context) { + // if validation is turned off then just return + if (!shouldValidate) { + String message = Messages.resources_readOnly2; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.READ_ONLY_LOCAL, message, null); + for (int i = 0; i < files.length; i++) { + if (files[i].isReadOnly()) { + IPath filePath = files[i].getFullPath(); + message = NLS.bind(Messages.resources_readOnly, filePath); + result.add(new ResourceStatus(IResourceStatus.READ_ONLY_LOCAL, filePath, message)); + } + } + return result.getChildren().length == 0 ? Status.OK_STATUS : (IStatus) result; + } + // first time through the validator hasn't been initialized so try and create it + if (validator == null) + initializeValidator(); + // we were unable to initialize the validator. Validation has been turned off and + // a warning has already been logged so just return. + if (validator == null) + return Status.OK_STATUS; + // otherwise call the API and throw an exception if appropriate + final IStatus[] status = new IStatus[1]; + ISafeRunnable body = new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + status[0] = new ResourceStatus(IStatus.ERROR, null, Messages.resources_errorValidator, exception); + } + + @Override + public void run() throws Exception { + Object c = context; + //must null any reference to FileModificationValidationContext for backwards compatibility + if (!(validator instanceof FileModificationValidator)) + if (c instanceof FileModificationValidationContext) + c = null; + status[0] = validator.validateEdit(files, c); + } + }; + SafeRunner.run(body); + return status[0]; + } + + @Override + public IStatus validateLinkLocation(IResource resource, IPath unresolvedLocation) { + return locationValidator.validateLinkLocation(resource, unresolvedLocation); + } + + @Override + public IStatus validateLinkLocationURI(IResource resource, URI unresolvedLocation) { + return locationValidator.validateLinkLocationURI(resource, unresolvedLocation); + } + + @Override + public IStatus validateName(String segment, int type) { + return locationValidator.validateName(segment, type); + } + + @Override + public IStatus validateNatureSet(String[] natureIds) { + return natureManager.validateNatureSet(natureIds); + } + + @Override + public IStatus validatePath(String path, int type) { + return locationValidator.validatePath(path, type); + } + + @Override + public IStatus validateProjectLocation(IProject context, IPath location) { + return locationValidator.validateProjectLocation(context, location); + } + + @Override + public IStatus validateProjectLocationURI(IProject project, URI location) { + return locationValidator.validateProjectLocationURI(project, location); + } + + /** + * Internal method. To be called only from the following methods: + *
    + *
  • IFile#appendContents
  • + *
  • IFile#setContents(InputStream, boolean, boolean, IProgressMonitor)
  • + *
  • IFile#setContents(IFileState, boolean, boolean, IProgressMonitor)
  • + *
+ * + * @see IFileModificationValidator#validateSave(IFile) + */ + protected void validateSave(final IFile file) throws CoreException { + // if validation is turned off then just return + if (!shouldValidate) + return; + // first time through the validator hasn't been initialized so try and create it + if (validator == null) + initializeValidator(); + // we were unable to initialize the validator. Validation has been turned off and + // a warning has already been logged so just return. + if (validator == null) + return; + // otherwise call the API and throw an exception if appropriate + final IStatus[] status = new IStatus[1]; + ISafeRunnable body = new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + status[0] = new ResourceStatus(IStatus.ERROR, null, Messages.resources_errorValidator, exception); + } + + @Override + public void run() throws Exception { + status[0] = validator.validateSave(file); + } + }; + SafeRunner.run(body); + if (!status[0].isOK()) + throw new ResourceException(status[0]); + } + + @Override + public IStatus validateFiltered(IResource resource) { + try { + if (((Resource) resource).isFilteredWithException(true)) + return new ResourceStatus(IStatus.ERROR, Messages.resources_errorResourceIsFiltered); + } catch (CoreException e) { + // if we can't validate it, we return OK + } + return Status.OK_STATUS; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java new file mode 100644 index 0000000000..78d21a7d57 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.IWorkspaceDescription; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.preferences.DefaultScope; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; + +/** + * @see IWorkspaceDescription + */ +public class WorkspaceDescription extends ModelObject implements IWorkspaceDescription { + protected boolean autoBuilding; + protected String[] buildOrder; + protected long fileStateLongevity; + protected int maxBuildIterations; + protected int maxFileStates; + protected long maxFileStateSize; + protected boolean applyFileStatePolicy; + private long snapshotInterval; + protected int operationsPerSnapshot; + protected long deltaExpiration; + + public WorkspaceDescription(String name) { + super(name); + // initialize based on the values in the default preferences + IEclipsePreferences node = DefaultScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES); + autoBuilding = node.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING, PreferenceInitializer.PREF_AUTO_BUILDING_DEFAULT); + maxBuildIterations = node.getInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS, PreferenceInitializer.PREF_MAX_BUILD_ITERATIONS_DEFAULT); + applyFileStatePolicy = node.getBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY, PreferenceInitializer.PREF_APPLY_FILE_STATE_POLICY_DEFAULT); + fileStateLongevity = node.getLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, PreferenceInitializer.PREF_FILE_STATE_LONGEVITY_DEFAULT); + maxFileStates = node.getInt(ResourcesPlugin.PREF_MAX_FILE_STATES, PreferenceInitializer.PREF_MAX_FILE_STATES_DEFAULT); + maxFileStateSize = node.getLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, PreferenceInitializer.PREF_MAX_FILE_STATE_SIZE_DEFAULT); + snapshotInterval = node.getLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL, PreferenceInitializer.PREF_SNAPSHOT_INTERVAL_DEFAULT); + operationsPerSnapshot = node.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT, PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT_DEFAULT); + deltaExpiration = node.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION, PreferenceInitializer.PREF_DELTA_EXPIRATION_DEFAULT); + } + + /** + * @see IWorkspaceDescription#getBuildOrder() + */ + @Override + public String[] getBuildOrder() { + return getBuildOrder(true); + } + + public String[] getBuildOrder(boolean makeCopy) { + if (buildOrder == null) + return null; + return makeCopy ? (String[]) buildOrder.clone() : buildOrder; + } + + public long getDeltaExpiration() { + return deltaExpiration; + } + + public void setDeltaExpiration(long value) { + deltaExpiration = value; + } + + /** + * @see IWorkspaceDescription#getFileStateLongevity() + */ + @Override + public long getFileStateLongevity() { + return fileStateLongevity; + } + + /** + * @see IWorkspaceDescription#getMaxBuildIterations() + */ + @Override + public int getMaxBuildIterations() { + return maxBuildIterations; + } + + /** + * @see IWorkspaceDescription#getMaxFileStates() + */ + @Override + public int getMaxFileStates() { + return maxFileStates; + } + + /** + * @see IWorkspaceDescription#getMaxFileStateSize() + */ + @Override + public long getMaxFileStateSize() { + return maxFileStateSize; + } + + /** + * @see IWorkspaceDescription#isApplyFileStatePolicy() + */ + @Override + public boolean isApplyFileStatePolicy() { + return applyFileStatePolicy; + } + + public int getOperationsPerSnapshot() { + return operationsPerSnapshot; + } + + /** + * @see IWorkspaceDescription#getSnapshotInterval() + */ + @Override + public long getSnapshotInterval() { + return snapshotInterval; + } + + public void internalSetBuildOrder(String[] value) { + buildOrder = value; + } + + /** + * @see IWorkspaceDescription#isAutoBuilding() + */ + @Override + public boolean isAutoBuilding() { + return autoBuilding; + } + + public void setOperationsPerSnapshot(int value) { + operationsPerSnapshot = value; + } + + /** + * @see IWorkspaceDescription#setAutoBuilding(boolean) + */ + @Override + public void setAutoBuilding(boolean value) { + autoBuilding = value; + } + + /** + * @see IWorkspaceDescription#setBuildOrder(String[]) + */ + @Override + public void setBuildOrder(String[] value) { + buildOrder = (value == null) ? null : (String[]) value.clone(); + } + + /** + * @see IWorkspaceDescription#setFileStateLongevity(long) + */ + @Override + public void setFileStateLongevity(long time) { + fileStateLongevity = time; + } + + /** + * @see IWorkspaceDescription#setMaxBuildIterations(int) + */ + @Override + public void setMaxBuildIterations(int number) { + maxBuildIterations = number; + } + + /** + * @see IWorkspaceDescription#setMaxFileStates(int) + */ + @Override + public void setMaxFileStates(int number) { + maxFileStates = number; + } + + /** + * @see IWorkspaceDescription#setMaxFileStateSize(long) + */ + @Override + public void setMaxFileStateSize(long size) { + maxFileStateSize = size; + } + + /** + * @see IWorkspaceDescription#setApplyFileStatePolicy(boolean) + */ + @Override + public void setApplyFileStatePolicy(boolean apply) { + applyFileStatePolicy = apply; + } + + /** + * @see IWorkspaceDescription#setSnapshotInterval(long) + */ + @Override + public void setSnapshotInterval(long snapshotInterval) { + this.snapshotInterval = snapshotInterval; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java new file mode 100644 index 0000000000..a1b3d49a87 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * 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.resources; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import javax.xml.parsers.*; +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.resources.IResourceStatus; +import org.eclipse.core.runtime.IPath; +import org.eclipse.osgi.util.NLS; +import org.w3c.dom.*; +import org.xml.sax.SAXException; + +/** + * This class contains legacy code only. It is being used to read workspace + * descriptions which are obsolete. + */ +public class WorkspaceDescriptionReader implements IModelObjectConstants { + /** constants */ + protected static final String[] EMPTY_STRING_ARRAY = new String[0]; + + public WorkspaceDescriptionReader() { + super(); + } + + protected String getString(Node target, String tagName) { + Node node = searchNode(target, tagName); + return node != null ? (node.getFirstChild() == null ? null : node.getFirstChild().getNodeValue()) : null; + } + + protected String[] getStrings(Node target) { + if (target == null) + return null; + NodeList list = target.getChildNodes(); + if (list.getLength() == 0) + return EMPTY_STRING_ARRAY; + List result = new ArrayList(list.getLength()); + for (int i = 0; i < list.getLength(); i++) { + Node node = list.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) + result.add(read(node.getChildNodes().item(0))); + } + return result.toArray(new String[result.size()]); + } + + /** + * A value was discovered in the workspace description file that was not a number. + * Log the exception. + */ + private void logNumberFormatException(String value, NumberFormatException e) { + String msg = NLS.bind(Messages.resources_readWorkspaceMetaValue, value); + Policy.log(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, null, msg, e)); + } + + public Object read(InputStream input) { + try { + DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = parser.parse(input); + return read(document.getFirstChild()); + } catch (IOException e) { + // ignore + } catch (SAXException e) { + // ignore + } catch (ParserConfigurationException e) { + // ignore + } + return null; + } + + public Object read(IPath location, IPath tempLocation) throws IOException { + SafeFileInputStream file = new SafeFileInputStream(location.toOSString(), tempLocation.toOSString()); + try { + return read(file); + } finally { + file.close(); + } + } + + protected Object read(Node node) { + if (node == null) + return null; + switch (node.getNodeType()) { + case Node.ELEMENT_NODE : + if (node.getNodeName().equals(WORKSPACE_DESCRIPTION)) + return readWorkspaceDescription(node); + case Node.TEXT_NODE : + String value = node.getNodeValue(); + return value == null ? null : value.trim(); + default : + return node.toString(); + } + } + + /** + * read (String, String) hashtables + */ + protected WorkspaceDescription readWorkspaceDescription(Node node) { + // get values + String name = getString(node, NAME); + String autobuild = getString(node, AUTOBUILD); + String snapshotInterval = getString(node, SNAPSHOT_INTERVAL); + String applyFileStatePolicy = getString(node, APPLY_FILE_STATE_POLICY); + String fileStateLongevity = getString(node, FILE_STATE_LONGEVITY); + String maxFileStateSize = getString(node, MAX_FILE_STATE_SIZE); + String maxFileStates = getString(node, MAX_FILE_STATES); + String[] buildOrder = getStrings(searchNode(node, BUILD_ORDER)); + + // build instance + //invalid values are skipped and defaults are used instead + WorkspaceDescription description = new WorkspaceDescription(name); + if (autobuild != null) + //if in doubt (value is corrupt) we want autobuild on + description.setAutoBuilding(!autobuild.equals(Integer.toString(0))); + if (applyFileStatePolicy != null) + //if in doubt (value is corrupt) we want applyFileLimits on + description.setApplyFileStatePolicy(!applyFileStatePolicy.equals(Integer.toString(0))); + try { + if (fileStateLongevity != null) + description.setFileStateLongevity(Long.parseLong(fileStateLongevity)); + } catch (NumberFormatException e) { + logNumberFormatException(fileStateLongevity, e); + } + try { + if (maxFileStateSize != null) + description.setMaxFileStateSize(Long.parseLong(maxFileStateSize)); + } catch (NumberFormatException e) { + logNumberFormatException(maxFileStateSize, e); + } + try { + if (maxFileStates != null) + description.setMaxFileStates(Integer.parseInt(maxFileStates)); + } catch (NumberFormatException e) { + logNumberFormatException(maxFileStates, e); + } + if (buildOrder != null) + description.internalSetBuildOrder(buildOrder); + try { + if (snapshotInterval != null) + description.setSnapshotInterval(Long.parseLong(snapshotInterval)); + } catch (NumberFormatException e) { + logNumberFormatException(snapshotInterval, e); + } + return description; + } + + protected Node searchNode(Node target, String tagName) { + NodeList list = target.getChildNodes(); + for (int i = 0; i < list.getLength(); i++) { + if (list.item(i).getNodeName().equals(tagName)) + return list.item(i); + } + return null; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java new file mode 100644 index 0000000000..b9c4bb9af2 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java @@ -0,0 +1,256 @@ +/******************************************************************************* + * 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.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Preferences; +import org.eclipse.core.runtime.Preferences.PropertyChangeEvent; + +/** + * This class provides the same interface as WorkspaceDescription + * but instead of changing/obtaining values from its internal state, it + * changes/obtains properties in/from the workspace plug-in's preferences. + * + * Note: for performance reasons, some frequently called accessor methods are + * reading a cached value from the super class instead of reading the + * corresponding property preference store. To keep the cache synchronized with + * the preference store, a property change listener is used. + */ +public class WorkspacePreferences extends WorkspaceDescription { + + public final static String PROJECT_SEPARATOR = "/"; //$NON-NLS-1$ + + private Preferences preferences; + + /** + * Helper method that converts a string string array {"string1"," + * string2",..."stringN"} to a string in the form "string1,string2,... + * stringN". + */ + public static String convertStringArraytoString(String[] array) { + if (array == null || array.length == 0) + return ""; //$NON-NLS-1$ + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < array.length; i++) { + sb.append(array[i]); + sb.append(PROJECT_SEPARATOR); + } + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); + } + + /** + * Helper method that converts a string in the form "string1,string2,... + * stringN" to a string array {"string1","string2",..."stringN"}. + */ + public static String[] convertStringToStringArray(String string, String separator) { + List list = new ArrayList(); + for (StringTokenizer tokenizer = new StringTokenizer(string, separator); tokenizer.hasMoreTokens();) + list.add(tokenizer.nextToken()); + return list.toArray(new String[list.size()]); + } + + /** + * Helper method that copies all attributes from a workspace description + * object to another. + */ + private static void copyFromTo(WorkspaceDescription source, WorkspaceDescription target) { + target.setAutoBuilding(source.isAutoBuilding()); + target.setBuildOrder(source.getBuildOrder()); + target.setMaxBuildIterations(source.getMaxBuildIterations()); + target.setApplyFileStatePolicy(source.isApplyFileStatePolicy()); + target.setFileStateLongevity(source.getFileStateLongevity()); + target.setMaxFileStates(source.getMaxFileStates()); + target.setMaxFileStateSize(source.getMaxFileStateSize()); + target.setSnapshotInterval(source.getSnapshotInterval()); + target.setOperationsPerSnapshot(source.getOperationsPerSnapshot()); + target.setDeltaExpiration(source.getDeltaExpiration()); + } + + public WorkspacePreferences() { + super("Workspace"); //$NON-NLS-1$ + this.preferences = ResourcesPlugin.getPlugin().getPluginPreferences(); + + final String version = preferences.getString(ICoreConstants.PREF_VERSION_KEY); + if (!ICoreConstants.PREF_VERSION.equals(version)) + upgradeVersion(version); + + // initialize cached preferences (for better performance) + super.setAutoBuilding(preferences.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING)); + super.setSnapshotInterval(preferences.getInt(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL)); + super.setMaxBuildIterations(preferences.getInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS)); + super.setApplyFileStatePolicy(preferences.getBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY)); + super.setMaxFileStates(preferences.getInt(ResourcesPlugin.PREF_MAX_FILE_STATES)); + super.setMaxFileStateSize(preferences.getLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE)); + super.setFileStateLongevity(preferences.getLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY)); + super.setOperationsPerSnapshot(preferences.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT)); + super.setDeltaExpiration(preferences.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION)); + + // This property listener ensures we are being updated properly when changes + // are done directly to the preference store. + preferences.addPropertyChangeListener(new Preferences.IPropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + synchronizeWithPreferences(event.getProperty()); + } + }); + } + + @Override + public Object clone() { + // should never be called - throws an exception to avoid using a + // WorkspacePreferences when using WorkspaceDescription was the real + // intention (this class offers a different protocol for copying state). + throw new UnsupportedOperationException("clone() is not supported in " + getClass().getName()); //$NON-NLS-1$ + } + + public void copyFrom(WorkspaceDescription source) { + copyFromTo(source, this); + } + + public void copyTo(WorkspaceDescription target) { + copyFromTo(this, target); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#getBuildOrder() + */ + @Override + public String[] getBuildOrder() { + boolean defaultBuildOrder = preferences.getBoolean(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER); + if (defaultBuildOrder) + return null; + return convertStringToStringArray(preferences.getString(ResourcesPlugin.PREF_BUILD_ORDER), PROJECT_SEPARATOR); + } + + /** + * @see org.eclipse.core.internal.resources. + * WorkspaceDescription#getBuildOrder(boolean) + */ + @Override + public String[] getBuildOrder(boolean makeCopy) { + //note that since this is stored in the preference store, we are creating + //a new copy of the string array on every access anyway + return getBuildOrder(); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setAutoBuilding(boolean) + */ + @Override + public void setAutoBuilding(boolean value) { + preferences.setValue(ResourcesPlugin.PREF_AUTO_BUILDING, value); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setBuildOrder(String[]) + */ + @Override + public void setBuildOrder(String[] value) { + preferences.setValue(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER, value == null); + preferences.setValue(ResourcesPlugin.PREF_BUILD_ORDER, convertStringArraytoString(value)); + } + + @Override + public void setDeltaExpiration(long value) { + preferences.setValue(PreferenceInitializer.PREF_DELTA_EXPIRATION, value); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setApplyFileStatePolicy(boolean) + */ + @Override + public void setApplyFileStatePolicy(boolean apply) { + preferences.setValue(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY, apply); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setFileStateLongevity(long) + */ + @Override + public void setFileStateLongevity(long time) { + preferences.setValue(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, time); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxBuildIterations(int) + */ + @Override + public void setMaxBuildIterations(int number) { + preferences.setValue(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS, number); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxFileStates(int) + */ + @Override + public void setMaxFileStates(int number) { + preferences.setValue(ResourcesPlugin.PREF_MAX_FILE_STATES, number); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxFileStateSize(long) + */ + @Override + public void setMaxFileStateSize(long size) { + preferences.setValue(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, size); + } + + @Override + public void setOperationsPerSnapshot(int value) { + preferences.setValue(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT, value); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setSnapshotInterval(long) + */ + @Override + public void setSnapshotInterval(long delay) { + preferences.setValue(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL, delay); + } + + protected void synchronizeWithPreferences(String property) { + // do not use the value in the event - may be a string instead + // of the expected type. Retrieve it from the preferences store + // using the type-specific method + if (property.equals(ResourcesPlugin.PREF_AUTO_BUILDING)) + super.setAutoBuilding(preferences.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING)); + else if (property.equals(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL)) + super.setSnapshotInterval(preferences.getLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL)); + else if (property.equals(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS)) + super.setMaxBuildIterations(preferences.getInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS)); + else if (property.equals(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY)) + super.setApplyFileStatePolicy(preferences.getBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY)); + else if (property.equals(ResourcesPlugin.PREF_MAX_FILE_STATES)) + super.setMaxFileStates(preferences.getInt(ResourcesPlugin.PREF_MAX_FILE_STATES)); + else if (property.equals(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE)) + super.setMaxFileStateSize(preferences.getLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE)); + else if (property.equals(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY)) + super.setFileStateLongevity(preferences.getLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY)); + else if (property.equals(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT)) + super.setOperationsPerSnapshot(preferences.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT)); + else if (property.equals(PreferenceInitializer.PREF_DELTA_EXPIRATION)) + super.setDeltaExpiration(preferences.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION)); + } + + private void upgradeVersion(String oldVersion) { + if (oldVersion.length() == 0) { + //only need to convert the build order if we are not using the default order + if (!preferences.getBoolean(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER)) { + String oldOrder = preferences.getString(ResourcesPlugin.PREF_BUILD_ORDER); + setBuildOrder(convertStringToStringArray(oldOrder, ":")); //$NON-NLS-1$ + } + } + preferences.setValue(ICoreConstants.PREF_VERSION_KEY, ICoreConstants.PREF_VERSION); + } +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java new file mode 100644 index 0000000000..535e4d8b0c --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java @@ -0,0 +1,295 @@ +/******************************************************************************* + * 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.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.utils.FileUtil; +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 WorkspaceRoot extends Container implements IWorkspaceRoot { + /** + * As an optimization, we store a table of project handles + * that have been requested from this root. This maps project + * name strings to project handles. + */ + private final Map projectTable = Collections.synchronizedMap(new HashMap(16)); + + /** + * Cache of the canonicalized platform location. + */ + private final IPath workspaceLocation; + + protected WorkspaceRoot(IPath path, Workspace container) { + super(path, container); + Assert.isTrue(path.equals(Path.ROOT)); + workspaceLocation = FileUtil.canonicalPath(Platform.getLocation()); + Assert.isNotNull(workspaceLocation); + } + + @Override + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT; + delete(updateFlags, monitor); + } + + @Override + public void delete(boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + delete(updateFlags, monitor); + } + + @Override + public boolean exists(int flags, boolean checkType) { + return true; + } + + @Deprecated + @Override + public IContainer[] findContainersForLocation(IPath location) { + return findContainersForLocationURI(URIUtil.toURI(location.makeAbsolute())); + } + + @Override + public IContainer[] findContainersForLocationURI(URI location) { + return findContainersForLocationURI(location, NONE); + } + + @Override + public IContainer[] findContainersForLocationURI(URI location, int memberFlags) { + if (!location.isAbsolute()) + throw new IllegalArgumentException(); + return (IContainer[]) getLocalManager().allResourcesFor(location, false, memberFlags); + } + + @Deprecated + @Override + public IFile[] findFilesForLocation(IPath location) { + return findFilesForLocationURI(URIUtil.toURI(location.makeAbsolute())); + } + + @Override + public IFile[] findFilesForLocationURI(URI location) { + return findFilesForLocationURI(location, NONE); + } + + @Override + public IFile[] findFilesForLocationURI(URI location, int memberFlags) { + if (!location.isAbsolute()) + throw new IllegalArgumentException(); + return (IFile[]) getLocalManager().allResourcesFor(location, true, memberFlags); + } + + @Override + public IContainer getContainerForLocation(IPath location) { + return getLocalManager().containerForLocation(location); + } + + @Override + public String getDefaultCharset(boolean checkImplicit) { + if (checkImplicit) + return ResourcesPlugin.getEncoding(); + String enc = ResourcesPlugin.getPlugin().getPluginPreferences().getString(ResourcesPlugin.PREF_ENCODING); + return enc == null || enc.length() == 0 ? null : enc; + } + + @Override + public IFile getFileForLocation(IPath location) { + return getLocalManager().fileForLocation(location); + } + + @Override + public long getLocalTimeStamp() { + return IResource.NULL_STAMP; + } + + @Override + public IPath getLocation() { + return workspaceLocation; + } + + @Override + public String getName() { + return ""; //$NON-NLS-1$ + } + + @Override + public IContainer getParent() { + return null; + } + + @Override + public IProject getProject() { + return null; + } + + @Override + public IProject getProject(String name) { + //first check our project cache + Project result = projectTable.get(name); + if (result == null) { + IPath projectPath = new Path(null, name).makeAbsolute(); + String message = "Path for project must have only one segment."; //$NON-NLS-1$ + Assert.isLegal(projectPath.segmentCount() == ICoreConstants.PROJECT_SEGMENT_LENGTH, message); + //try to get the project using a canonical name + String canonicalName = projectPath.lastSegment(); + result = projectTable.get(canonicalName); + if (result != null) + return result; + result = new Project(projectPath, workspace); + projectTable.put(canonicalName, result); + } + return result; + } + + @Override + public IPath getProjectRelativePath() { + return Path.EMPTY; + } + + @Override + public IProject[] getProjects() { + return getProjects(IResource.NONE); + } + + @Override + public IProject[] getProjects(int memberFlags) { + IResource[] roots = getChildren(memberFlags); + IProject[] result = new IProject[roots.length]; + try { + System.arraycopy(roots, 0, result, 0, roots.length); + } catch (ArrayStoreException ex) { + // Shouldn't happen since only projects should be children of the workspace root + for (int i = 0; i < roots.length; i++) { + if (roots[i].getType() != IResource.PROJECT) + Policy.log(IStatus.ERROR, NLS.bind("{0} is an invalid child of the workspace root.", //$NON-NLS-1$ + roots[i]), null); + + } + throw ex; + } + return result; + } + + @Override + public int getType() { + return IResource.ROOT; + } + + @Override + public void internalSetLocal(boolean flag, int depth) throws CoreException { + // do nothing for the root, but call for its children + if (depth == IResource.DEPTH_ZERO) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.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++) + ((Resource) children[i]).internalSetLocal(flag, depth); + } + + @Override + public boolean isDerived(int options) { + return false;//the root is never derived + } + + @Override + public boolean isHidden() { + return false;//the root is never hidden + } + + @Override + public boolean isHidden(int options) { + return false;//the root is never hidden + } + + @Override + public boolean isTeamPrivateMember(int options) { + return false;//the root is never a team private member + } + + @Override + public boolean isLinked(int options) { + return false;//the root is never linked + } + + @Deprecated + @Override + public boolean isLocal(int depth) { + // the flags parameter is ignored for the workspace root so pass anything + return isLocal(-1, depth); + } + + @Deprecated + @Override + public boolean isLocal(int flags, int depth) { + // don't check the flags....workspace root is always local + 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 boolean isPhantom() { + return false; + } + + @Deprecated + @Override + public void setDefaultCharset(String charset) { + // directly change the Resource plugin's preference for encoding + Preferences resourcesPreferences = ResourcesPlugin.getPlugin().getPluginPreferences(); + if (charset != null) + resourcesPreferences.setValue(ResourcesPlugin.PREF_ENCODING, charset); + else + resourcesPreferences.setToDefault(ResourcesPlugin.PREF_ENCODING); + } + + @Override + public void setHidden(boolean isHidden) { + //workspace root cannot be set hidden + } + + @Override + public long setLocalTimeStamp(long value) { + if (value < 0) + throw new IllegalArgumentException("Illegal time stamp: " + value); //$NON-NLS-1$ + //can't set local time for root + return value; + } + + @Deprecated + @Override + public void setReadOnly(boolean readonly) { + //can't set the root read only + } + + @Override + public void touch(IProgressMonitor monitor) { + // do nothing for the workspace root + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java new file mode 100644 index 0000000000..879f6af319 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * 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 - Initial API and implementation + * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.watson.ElementTree; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.osgi.util.NLS; + +/** + * Default tree reader that does not read anything. This is used in cases + * where the tree format is unknown (for example when opening a workspace + * from a future version). + */ +public abstract class WorkspaceTreeReader { + + /** + * Configuration setting to have an existing workspace + * project name take precedence over data being read, + * when set to true. + */ + protected boolean renameProjectNode; + + /** + * Returns the tree reader associated with the given tree version number. + * @param renameProjectNode if true, set up the reader to have + * the existing root node in the workspace (that is, the project being + * read into) take precedence over the root node being read from the file. + * Otherwise, the tree file is read unmodified. + */ + public static WorkspaceTreeReader getReader(Workspace workspace, int version, boolean renameProjectNode) throws CoreException { + WorkspaceTreeReader w = null; + switch (version) { + case ICoreConstants.WORKSPACE_TREE_VERSION_1 : + w = new WorkspaceTreeReader_1(workspace); + w.renameProjectNode = renameProjectNode; + return w; + case ICoreConstants.WORKSPACE_TREE_VERSION_2 : + w = new WorkspaceTreeReader_2(workspace); + w.renameProjectNode = renameProjectNode; + return w; + default : + // Unknown tree version - fail to read the tree + String msg = NLS.bind(Messages.resources_format, new Integer(version)); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, msg, null); + } + } + + /** + * Returns the tree reader associated with the given tree version number. + */ + public static WorkspaceTreeReader getReader(Workspace workspace, int version) throws CoreException { + return getReader(workspace, version, false); + } + + /** + * Returns a snapshot from the stream. This default implementation does nothing. + */ + public abstract ElementTree readSnapshotTree(DataInputStream input, ElementTree complete, IProgressMonitor monitor) throws CoreException; + + /** + * Reads all workspace trees from the stream. This default implementation does nothing. + */ + public abstract void readTree(DataInputStream input, IProgressMonitor monitor) throws CoreException; + + /** + * Reads a project's trees from the stream. This default implementation does nothing. + */ + public abstract void readTree(IProject project, DataInputStream input, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java new file mode 100644 index 0000000000..e697af6b12 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java @@ -0,0 +1,270 @@ +/******************************************************************************* + * 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) - [305718] Allow reading snapshot into renamed project + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.*; +import org.eclipse.core.internal.events.BuilderPersistentInfo; +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.internal.watson.ElementTreeReader; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; + +/** + * Reads version 1 of the workspace tree file format. + */ +public class WorkspaceTreeReader_1 extends WorkspaceTreeReader { + protected Workspace workspace; + + public WorkspaceTreeReader_1(Workspace workspace) { + this.workspace = workspace; + } + + protected int getVersion() { + return ICoreConstants.WORKSPACE_TREE_VERSION_1; + } + + protected void linkBuildersToTrees(List buildersToBeLinked, ElementTree[] trees, int index, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + ArrayList infos = null; + String projectName = null; + for (int i = 0; i < buildersToBeLinked.size(); i++) { + BuilderPersistentInfo info = buildersToBeLinked.get(i); + if (!info.getProjectName().equals(projectName)) { + if (infos != null) { // if it is not the first iteration + IProject project = workspace.getRoot().getProject(projectName); + workspace.getBuildManager().setBuildersPersistentInfo(project, infos); + } + projectName = info.getProjectName(); + infos = new ArrayList(5); + } + info.setLastBuildTree(trees[index++]); + infos.add(info); + } + if (infos != null) { + IProject project = workspace.getRoot().getProject(projectName); + workspace.getBuildManager().setBuildersPersistentInfo(project, infos); + } + } finally { + monitor.done(); + } + } + + protected void linkPluginsSavedStateToTrees(List states, ElementTree[] trees, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + for (int i = 0; i < states.size(); i++) { + SavedState state = states.get(i); + // If the tree is too old (depends on the policy), the plug-in should not + // get it back as a delta. It is expensive to maintain this information too long. + final SaveManager saveManager = workspace.getSaveManager(); + if (!saveManager.isOldPluginTree(state.pluginId)) { + state.oldTree = trees[i]; + } else { + //clear information for this plugin from master table + saveManager.clearDeltaExpiration(state.pluginId); + } + } + } finally { + monitor.done(); + } + } + + protected BuilderPersistentInfo readBuilderInfo(IProject project, DataInputStream input, int index) throws IOException { + //read the project name + String projectName = input.readUTF(); + //use the name of the project handle if available + if (project != null) + projectName = project.getName(); + String builderName = input.readUTF(); + return new BuilderPersistentInfo(projectName, builderName, index); + } + + protected void readBuildersPersistentInfo(IProject project, DataInputStream input, List builders, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + int builderCount = input.readInt(); + for (int i = 0; i < builderCount; i++) + builders.add(readBuilderInfo(project, input, i)); + } finally { + monitor.done(); + } + } + + protected void readPluginsSavedStates(DataInputStream input, HashMap savedStates, List plugins, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + int stateCount = input.readInt(); + for (int i = 0; i < stateCount; i++) { + String pluginId = input.readUTF(); + SavedState state = new SavedState(workspace, pluginId, null, null); + savedStates.put(pluginId, state); + plugins.add(state); + } + } finally { + monitor.done(); + } + } + + @Override + public ElementTree readSnapshotTree(DataInputStream input, ElementTree complete, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_readingSnap; + monitor.beginTask(message, Policy.totalWork); + ElementTreeReader reader = new ElementTreeReader(workspace.getSaveManager()); + while (input.available() > 0) { + readWorkspaceFields(input, Policy.subMonitorFor(monitor, Policy.totalWork / 2)); + complete = reader.readDelta(complete, input); + try { + // make sure each snapshot is read by the correct reader + int version = input.readInt(); + if (version != getVersion()) + return WorkspaceTreeReader.getReader(workspace, version).readSnapshotTree(input, complete, monitor); + } catch (EOFException e) { + break; + } + } + return complete; + } catch (IOException e) { + message = Messages.resources_readWorkspaceSnap; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + @Override + public void readTree(DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, Policy.totalWork); + readWorkspaceFields(input, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + HashMap savedStates = new HashMap(20); + List pluginsToBeLinked = new ArrayList(20); + readPluginsSavedStates(input, savedStates, pluginsToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + workspace.getSaveManager().setPluginsSavedState(savedStates); + + List buildersToBeLinked = new ArrayList(20); + readBuildersPersistentInfo(null, input, buildersToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + ElementTree[] trees = readTrees(Path.ROOT, input, Policy.subMonitorFor(monitor, Policy.opWork * 40 / 100)); + linkPluginsSavedStateToTrees(pluginsToBeLinked, trees, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + linkBuildersToTrees(buildersToBeLinked, trees, pluginsToBeLinked.size(), Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + } catch (IOException e) { + message = Messages.resources_readWorkspaceTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + @Override + public void readTree(IProject project, DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, 10); + /* read the number of builders */ + int numBuilders = input.readInt(); + + /* read in the list of builder names */ + String[] builderNames = new String[numBuilders]; + for (int i = 0; i < numBuilders; i++) { + String builderName = input.readUTF(); + builderNames[i] = builderName; + } + monitor.worked(1); + + /* read and link the trees */ + ElementTree[] trees = readTrees(project.getFullPath(), input, Policy.subMonitorFor(monitor, 8)); + + /* map builder names to trees */ + if (numBuilders > 0) { + ArrayList infos = new ArrayList(trees.length * 2 + 1); + for (int i = 0; i < numBuilders; i++) { + BuilderPersistentInfo info = new BuilderPersistentInfo(project.getName(), builderNames[i], -1); + info.setLastBuildTree(trees[i]); + infos.add(info); + } + workspace.getBuildManager().setBuildersPersistentInfo(project, infos); + } + monitor.worked(1); + + } catch (IOException e) { + message = Messages.resources_readProjectTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + /** + * Read trees from disk and link them to the workspace tree. + */ + protected ElementTree[] readTrees(IPath root, DataInputStream input, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + String message = Messages.resources_reading; + monitor.beginTask(message, 4); + ElementTreeReader treeReader = new ElementTreeReader(workspace.getSaveManager()); + String newProjectName = ""; //$NON-NLS-1$ + if (renameProjectNode) { + //have the existing project name (path to import into) take precedence over what we read + newProjectName = root.segment(0); + } + ElementTree[] trees = treeReader.readDeltaChain(input, newProjectName); + monitor.worked(3); + if (root.isRoot()) { + //Don't need to link because we're reading the whole workspace. + //The last tree in the chain is the complete tree. + ElementTree newTree = trees[trees.length - 1]; + newTree.setTreeData(workspace.tree.getTreeData()); + workspace.tree = newTree; + } else { + //splice the restored tree into the current set of trees + workspace.linkTrees(root, trees); + } + monitor.worked(1); + return trees; + } finally { + monitor.done(); + } + } + + protected void readWorkspaceFields(DataInputStream input, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + // read the node id + workspace.nextNodeId = input.readLong(); + // read the modification stamp (no longer used) + input.readLong(); + // read the next marker id + workspace.nextMarkerId = input.readLong(); + // read the synchronizer's registered sync partners + ((Synchronizer) workspace.getSynchronizer()).readPartners(input); + } finally { + monitor.done(); + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java new file mode 100644 index 0000000000..87523b3206 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * 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 + * Baltasar Belyavsky (Texas Instruments) - [361675] Order mismatch when saving/restoring workspace trees + * Broadcom Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.*; +import org.eclipse.core.internal.events.BuilderPersistentInfo; +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.IProject; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; + +/** + * Reads version 2 of the workspace tree file format. + * + * This version differs from version 1 in the amount of information that is persisted + * for each builder. Version 1 only stored builder names and trees. Version + * 2 stores builder names, project names, trees, and interesting projects for + * each builder. + *

+ * Since 3.7 support has been added for persisting multiple delta trees for + * multi-configuration builders. + *

+ *

+ * To achieve backwards compatibility, the new additional information is + * appended to the existing workspace tree file. This allows the workspace + * to be opened, and function, with older eclipse products. + *

+ */ +public class WorkspaceTreeReader_2 extends WorkspaceTreeReader_1 { + + private List builderInfos; + + public WorkspaceTreeReader_2(Workspace workspace) { + super(workspace); + } + + @Override + protected int getVersion() { + return ICoreConstants.WORKSPACE_TREE_VERSION_2; + } + + /* + * overwritten from WorkspaceTreeReader_1 + */ + @Override + protected void readBuildersPersistentInfo(IProject project, DataInputStream input, List builders, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + int builderCount = input.readInt(); + for (int i = 0; i < builderCount; i++) { + BuilderPersistentInfo info = readBuilderInfo(project, input, i); + // read interesting projects + int n = input.readInt(); + IProject[] projects = new IProject[n]; + for (int j = 0; j < n; j++) + projects[j] = workspace.getRoot().getProject(input.readUTF()); + info.setInterestingProjects(projects); + builders.add(info); + } + } finally { + monitor.done(); + } + } + + /** + * Read a workspace tree storing information about multiple projects. + * Overrides {@link WorkspaceTreeReader_1#readTree(DataInputStream, IProgressMonitor)} + */ + @Override + public void readTree(DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, Policy.totalWork); + + builderInfos = new ArrayList(20); + + // Read the version 2 part of the file, but don't set the builder info in + // the projects. Store it in builderInfos instead. + readWorkspaceFields(input, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + HashMap savedStates = new HashMap(20); + List pluginsToBeLinked = new ArrayList(20); + readPluginsSavedStates(input, savedStates, pluginsToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + workspace.getSaveManager().setPluginsSavedState(savedStates); + + int treeIndex = pluginsToBeLinked.size(); + + List buildersToBeLinked = new ArrayList(20); + readBuildersPersistentInfo(null, input, buildersToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + final ElementTree[] trees = readTrees(Path.ROOT, input, Policy.subMonitorFor(monitor, Policy.opWork * 40 / 100)); + linkPluginsSavedStateToTrees(pluginsToBeLinked, trees, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + linkBuildersToTrees(buildersToBeLinked, trees, treeIndex, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + // Since 3.7: Read the per-configuration trees if available + if (input.available() > 0) { + treeIndex += buildersToBeLinked.size(); + + buildersToBeLinked.clear(); + readBuildersPersistentInfo(null, input, buildersToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + linkBuildersToTrees(buildersToBeLinked, trees, treeIndex, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + for (Iterator it = builderInfos.iterator(); it.hasNext();) + it.next().setConfigName(input.readUTF()); + } + + // Set the builder infos on the projects + setBuilderInfos(builderInfos); + + } catch (IOException e) { + message = Messages.resources_readProjectTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + /** + * Read a workspace tree storing information about a single project. + * Overrides {@link WorkspaceTreeReader_2#readTree(IProject, DataInputStream, IProgressMonitor)} + */ + @Override + public void readTree(IProject project, DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, 10); + + builderInfos = new ArrayList(20); + + // Read the version 2 part of the file, but don't set the builder info in + // the projects. It is stored in builderInfos instead. + + int treeIndex = 0; + + List buildersToBeLinked = new ArrayList(20); + readBuildersPersistentInfo(project, input, buildersToBeLinked, Policy.subMonitorFor(monitor, 1)); + + ElementTree[] trees = readTrees(project.getFullPath(), input, Policy.subMonitorFor(monitor, 8)); + linkBuildersToTrees(buildersToBeLinked, trees, treeIndex, Policy.subMonitorFor(monitor, 1)); + + // Since 3.7: Read the additional builder information + if (input.available() > 0) { + treeIndex += buildersToBeLinked.size(); + + List infos = new ArrayList(5); + readBuildersPersistentInfo(project, input, infos, Policy.subMonitorFor(monitor, 1)); + linkBuildersToTrees(infos, trees, treeIndex, Policy.subMonitorFor(monitor, 1)); + + for (Iterator it = builderInfos.iterator(); it.hasNext();) + it.next().setConfigName(input.readUTF()); + } + + // Set the builder info on the projects + setBuilderInfos(builderInfos); + + } catch (IOException e) { + message = Messages.resources_readProjectTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + /** + * This implementation allows pre-3.7 version 2 and post-3.7 version 2 information to be loaded in separate passes. + * Links trees with the given builders, but does not add them to the projects. + * Overrides {@link WorkspaceTreeReader_1#linkBuildersToTrees(List, ElementTree[], int, IProgressMonitor)} + */ + @Override + protected void linkBuildersToTrees(List buildersToBeLinked, ElementTree[] trees, int index, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + for (int i = 0; i < buildersToBeLinked.size(); i++) { + BuilderPersistentInfo info = buildersToBeLinked.get(i); + info.setLastBuildTree(trees[index++]); + builderInfos.add(info); + } + } finally { + monitor.done(); + } + } + + /** + * Given a list of builder infos, group them by project and set them on the project. + */ + private void setBuilderInfos(List infos) { + Map> groupedInfos = new HashMap>(); + for (Iterator it = infos.iterator(); it.hasNext();) { + BuilderPersistentInfo info = it.next(); + if (!groupedInfos.containsKey(info.getProjectName())) + groupedInfos.put(info.getProjectName(), new ArrayList()); + groupedInfos.get(info.getProjectName()).add(info); + } + for (Map.Entry> entry : groupedInfos.entrySet()) { + IProject proj = workspace.getRoot().getProject(entry.getKey()); + workspace.getBuildManager().setBuildersPersistentInfo(proj, entry.getValue()); + } + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java new file mode 100644 index 0000000000..383f3053a3 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * 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.HashMap; +import java.util.Map; + +/** + * A simple XML writer. + */ +public class XMLWriter extends PrintWriter { + protected int tab; + protected String lineSeparator; + + /* constants */ + protected static final String XML_VERSION = ""; //$NON-NLS-1$ + + public XMLWriter(OutputStream output, String separator) throws UnsupportedEncodingException { + super(new OutputStreamWriter(output, "UTF8")); //$NON-NLS-1$ + tab = 0; + lineSeparator = separator; + println(XML_VERSION); + } + + public void endTag(String name) { + tab--; + printTag('/' + name, null); + } + + @Override + public void println(String x) { + super.print(x); + super.print(lineSeparator); + } + + public void printSimpleTag(String name, Object value) { + if (value != null) { + printTag(name, null, true, false); + print(getEscaped(String.valueOf(value))); + printTag('/' + name, null, false, true); + } + } + + public void printTabulation() { + for (int i = 0; i < tab; i++) + super.print('\t'); + } + + public void printTag(String name, HashMap parameters) { + printTag(name, parameters, true, true); + } + + public void printTag(String name, HashMap parameters, boolean shouldTab, boolean newLine) { + StringBuffer sb = new StringBuffer(); + sb.append("<"); //$NON-NLS-1$ + sb.append(name); + if (parameters != null) + for (Map.Entry entry : parameters.entrySet()) { + sb.append(" "); //$NON-NLS-1$ + String key = entry.getKey(); + sb.append(key); + sb.append("=\""); //$NON-NLS-1$ + sb.append(getEscaped(String.valueOf(entry.getValue()))); + sb.append("\""); //$NON-NLS-1$ + } + sb.append(">"); //$NON-NLS-1$ + if (shouldTab) + printTabulation(); + if (newLine) + println(sb.toString()); + else + print(sb.toString()); + } + + public void startTag(String name, HashMap parameters) { + startTag(name, parameters, true); + } + + public void startTag(String name, HashMap parameters, boolean newLine) { + printTag(name, parameters, true, newLine); + tab++; + } + + private static void appendEscapedChar(StringBuffer buffer, char c) { + String replacement = getReplacement(c); + if (replacement != null) { + buffer.append('&'); + buffer.append(replacement); + buffer.append(';'); + } else { + buffer.append(c); + } + } + + public static String getEscaped(String s) { + StringBuffer result = new StringBuffer(s.length() + 10); + for (int i = 0; i < s.length(); ++i) + appendEscapedChar(result, s.charAt(i)); + return result.toString(); + } + + private static String getReplacement(char c) { + // Encode special XML characters into the equivalent character references. + // These five are defined by default for all XML documents. + switch (c) { + case '<' : + return "lt"; //$NON-NLS-1$ + case '>' : + return "gt"; //$NON-NLS-1$ + case '"' : + return "quot"; //$NON-NLS-1$ + case '\'' : + return "apos"; //$NON-NLS-1$ + case '&' : + return "amp"; //$NON-NLS-1$ + } + return null; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java new file mode 100644 index 0000000000..0e231ef610 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.resources.mapping; + +import java.util.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; + +/** + * A description of the changes found in a delta + */ +public class ChangeDescription { + + private List addedRoots = new ArrayList(); + private List changedFiles = new ArrayList(); + private List closedProjects = new ArrayList(); + private List copiedRoots = new ArrayList(); + private List movedRoots = new ArrayList(); + private List removedRoots = new ArrayList(); + + private IResource createSourceResource(IResourceDelta delta) { + IPath sourcePath = delta.getMovedFromPath(); + IResource resource = delta.getResource(); + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); + switch (resource.getType()) { + case IResource.PROJECT : + return wsRoot.getProject(sourcePath.segment(0)); + case IResource.FOLDER : + return wsRoot.getFolder(sourcePath); + case IResource.FILE : + return wsRoot.getFile(sourcePath); + } + return null; + } + + private void ensureResourceCovered(IResource resource, List list) { + IPath path = resource.getFullPath(); + for (Iterator iter = list.iterator(); iter.hasNext();) { + IResource root = iter.next(); + if (root.getFullPath().isPrefixOf(path)) { + return; + } + } + list.add(resource); + } + + public IResource[] getRootResources() { + Set result = new HashSet(); + result.addAll(addedRoots); + result.addAll(changedFiles); + result.addAll(closedProjects); + result.addAll(copiedRoots); + result.addAll(movedRoots); + result.addAll(removedRoots); + return result.toArray(new IResource[result.size()]); + } + + private void handleAdded(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) { + handleMove(delta); + } else if ((delta.getFlags() & IResourceDelta.COPIED_FROM) != 0) { + handleCopy(delta); + } else { + ensureResourceCovered(delta.getResource(), addedRoots); + } + } + + private void handleChange(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.REPLACED) != 0) { + // A replace was added in place of a removed resource + handleAdded(delta); + } else if (delta.getResource().getType() == IResource.FILE) { + ensureResourceCovered(delta.getResource(), changedFiles); + } + } + + private void handleCopy(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.COPIED_FROM) != 0) { + IResource source = createSourceResource(delta); + ensureResourceCovered(source, copiedRoots); + } + } + + private void handleMove(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) { + movedRoots.add(delta.getResource()); + } else if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) { + IResource source = createSourceResource(delta); + ensureResourceCovered(source, movedRoots); + } + } + + private void handleRemoved(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.OPEN) != 0) { + closedProjects.add(delta.getResource()); + } else if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) { + handleMove(delta); + } else { + ensureResourceCovered(delta.getResource(), removedRoots); + } + } + + /** + * Record the change and return whether any child changes should be visited. + * @param delta the change + * @return whether any child changes should be visited + */ + public boolean recordChange(IResourceDelta delta) { + switch (delta.getKind()) { + case IResourceDelta.ADDED : + handleAdded(delta); + return true; // Need to traverse children to look for moves or other changes under added roots + case IResourceDelta.REMOVED : + handleRemoved(delta); + // No need to look for further changes under a remove (such as moves). + // Changes will be discovered in corresponding destination delta + return false; + case IResourceDelta.CHANGED : + handleChange(delta); + return true; + } + return true; + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java new file mode 100644 index 0000000000..c854d27e76 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.*; +import org.eclipse.core.expressions.*; +import org.eclipse.core.internal.resources.ResourceException; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +public class ModelProviderDescriptor implements IModelProviderDescriptor { + + private String id; + private String[] extendedModels; + private String label; + private ModelProvider provider; + private Expression enablementRule; + + private static EvaluationContext createEvaluationContext(Object element) { + EvaluationContext result = new EvaluationContext(null, element); + return result; + } + + public ModelProviderDescriptor(IExtension extension) throws CoreException { + readExtension(extension); + } + + private boolean convert(EvaluationResult eval) { + if (eval == EvaluationResult.FALSE) + return false; + return true; + } + + protected void fail(String reason) throws CoreException { + throw new ResourceException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, reason, null)); + } + + @Override + public String[] getExtendedModels() { + return extendedModels; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public IResource[] getMatchingResources(IResource[] resources) throws CoreException { + Set result = new HashSet(); + for (int i = 0; i < resources.length; i++) { + IResource resource = resources[i]; + EvaluationContext evalContext = createEvaluationContext(resource); + if (matches(evalContext)) { + result.add(resource); + } + } + return result.toArray(new IResource[result.size()]); + } + + @Override + public synchronized ModelProvider getModelProvider() throws CoreException { + if (provider == null) { + IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MODEL_PROVIDERS, id); + IConfigurationElement[] elements = extension.getConfigurationElements(); + for (int i = 0; i < elements.length; i++) { + IConfigurationElement element = elements[i]; + if (element.getName().equalsIgnoreCase("modelProvider")) { //$NON-NLS-1$ + try { + provider = (ModelProvider) element.createExecutableExtension("class"); //$NON-NLS-1$ + provider.init(this); + } catch (ClassCastException e) { + String message = NLS.bind(Messages.mapping_wrongType, id); + throw new CoreException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, Platform.PLUGIN_ERROR, message, e)); + } + } + } + } + return provider; + } + + public boolean matches(IEvaluationContext context) throws CoreException { + if (enablementRule == null) + return false; + return convert(enablementRule.evaluate(context)); + } + + /** + * Initialize this descriptor based on the provided extension point. + */ + protected void readExtension(IExtension extension) throws CoreException { + //read the extension + id = extension.getUniqueIdentifier(); + if (id == null) + fail(Messages.mapping_noIdentifier); + label = extension.getLabel(); + IConfigurationElement[] elements = extension.getConfigurationElements(); + int count = elements.length; + ArrayList extendsList = new ArrayList(count); + for (int i = 0; i < count; i++) { + IConfigurationElement element = elements[i]; + String name = element.getName(); + if (name.equalsIgnoreCase("extends-model")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(NLS.bind(Messages.mapping_invalidDef, id)); + extendsList.add(attribute); + } else if (name.equalsIgnoreCase(ExpressionTagNames.ENABLEMENT)) { + enablementRule = ExpressionConverter.getDefault().perform(element); + } + } + extendedModels = extendsList.toArray(new String[extendsList.size()]); + } + + @Override + public ResourceTraversal[] getMatchingTraversals(ResourceTraversal[] traversals) throws CoreException { + List result = new ArrayList(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + if (getMatchingResources(traversal.getResources()).length > 0) { + result.add(traversal); + } + } + return result.toArray(new ResourceTraversal[result.size()]); + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java new file mode 100644 index 0000000000..37a7fc5149 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2005, 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.resources.mapping; + +import java.util.HashMap; +import java.util.Map; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.mapping.IModelProviderDescriptor; +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.runtime.*; + +public class ModelProviderManager { + + private static Map descriptors; + private static ModelProviderManager instance; + + public synchronized static ModelProviderManager getDefault() { + if (instance == null) { + instance = new ModelProviderManager(); + } + return instance; + } + + private void detectCycles() { + // TODO Auto-generated method stub + + } + + public IModelProviderDescriptor getDescriptor(String id) { + lazyInitialize(); + return descriptors.get(id); + } + + public IModelProviderDescriptor[] getDescriptors() { + lazyInitialize(); + return descriptors.values().toArray(new IModelProviderDescriptor[descriptors.size()]); + } + + public ModelProvider getModelProvider(String modelProviderId) throws CoreException { + IModelProviderDescriptor desc = getDescriptor(modelProviderId); + if (desc == null) + return null; + return desc.getModelProvider(); + } + + protected void lazyInitialize() { + if (descriptors != null) + return; + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MODEL_PROVIDERS); + IExtension[] extensions = point.getExtensions(); + descriptors = new HashMap(extensions.length * 2 + 1); + for (int i = 0, imax = extensions.length; i < imax; i++) { + IModelProviderDescriptor desc = null; + try { + desc = new ModelProviderDescriptor(extensions[i]); + } catch (CoreException e) { + Policy.log(e); + } + if (desc != null) + descriptors.put(desc.getId(), desc); + } + //do cycle detection now so it only has to be done once + //cycle detection on a graph subset is a pain + detectCycles(); + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java new file mode 100644 index 0000000000..051b1f69b5 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.mapping; + +import java.util.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * Concrete implementation of IResourceDelta used for operation validation + */ +public final class ProposedResourceDelta extends PlatformObject implements IResourceDelta { + protected static int KIND_MASK = 0xFF; + + private HashMap children = new HashMap(8); + private IPath movedFromPath; + private IPath movedToPath; + private IResource resource; + private int status; + + public ProposedResourceDelta(IResource resource) { + this.resource = resource; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#accept(org.eclipse.core.resources.IResourceDeltaVisitor) + */ + @Override + public void accept(IResourceDeltaVisitor visitor) throws CoreException { + accept(visitor, 0); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#accept(org.eclipse.core.resources.IResourceDeltaVisitor, boolean) + */ + @Override + public void accept(IResourceDeltaVisitor visitor, boolean includePhantoms) throws CoreException { + accept(visitor, includePhantoms ? IContainer.INCLUDE_PHANTOMS : 0); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#accept(org.eclipse.core.resources.IResourceDeltaVisitor, int) + */ + @Override + public void accept(IResourceDeltaVisitor visitor, int memberFlags) throws CoreException { + if (!visitor.visit(this)) + return; + for (Iterator iter = children.values().iterator(); iter.hasNext();) { + ProposedResourceDelta childDelta = iter.next(); + childDelta.accept(visitor, memberFlags); + } + } + + /** + * Adds a child delta to the list of children for this delta node. + * @param delta + */ + protected void add(ProposedResourceDelta delta) { + if (children.size() == 0 && status == 0) + setKind(IResourceDelta.CHANGED); + children.put(delta.getResource().getName(), delta); + } + + /** + * Adds the given flags to this delta. + * @param flags The flags to add + */ + protected void addFlags(int flags) { + //make sure the provided flags don't influence the kind + this.status |= (flags & ~KIND_MASK); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#findMember(org.eclipse.core.runtime.IPath) + */ + @Override + public IResourceDelta findMember(IPath path) { + int segmentCount = path.segmentCount(); + if (segmentCount == 0) + return this; + + //iterate over the path and find matching child delta + ProposedResourceDelta current = this; + for (int i = 0; i < segmentCount; i++) { + current = current.children.get(path.segment(i)); + if (current == null) + return null; + } + return current; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#getAffectedChildren() + */ + @Override + public IResourceDelta[] getAffectedChildren() { + return getAffectedChildren(ADDED | REMOVED | CHANGED, IResource.NONE); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#getAffectedChildren(int) + */ + @Override + public IResourceDelta[] getAffectedChildren(int kindMask) { + return getAffectedChildren(kindMask, IResource.NONE); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#getAffectedChildren(int, int) + */ + @Override + public IResourceDelta[] getAffectedChildren(int kindMask, int memberFlags) { + List result = new ArrayList(); + for (Iterator iter = children.values().iterator(); iter.hasNext();) { + ProposedResourceDelta child = iter.next(); + if ((child.getKind() & kindMask) != 0) + result.add(child); + } + return result.toArray(new IResourceDelta[result.size()]); + } + + /** + * Returns the child delta corresponding to the given child resource name, + * or null. + */ + ProposedResourceDelta getChild(String name) { + return children.get(name); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#getFlags() + */ + @Override + public int getFlags() { + return status & ~KIND_MASK; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#getFullPath() + */ + @Override + public IPath getFullPath() { + return getResource().getFullPath(); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#getKind() + */ + @Override + public int getKind() { + return status & KIND_MASK; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#getMarkerDeltas() + */ + @Override + public IMarkerDelta[] getMarkerDeltas() { + return new IMarkerDelta[0]; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#getMovedFromPath() + */ + @Override + public IPath getMovedFromPath() { + return movedFromPath; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#getMovedToPath() + */ + @Override + public IPath getMovedToPath() { + return movedToPath; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#getProjectRelativePath() + */ + @Override + public IPath getProjectRelativePath() { + return getResource().getProjectRelativePath(); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDelta#getResource() + */ + @Override + public IResource getResource() { + return resource; + } + + public void setFlags(int flags) { + status = getKind() | (flags & ~KIND_MASK); + } + + protected void setKind(int kind) { + status = getFlags() | (kind & KIND_MASK); + } + + protected void setMovedFromPath(IPath path) { + movedFromPath = path; + } + + protected void setMovedToPath(IPath path) { + movedToPath = path; + } + + /** + * For debugging purposes only. + */ + @Override + public String toString() { + return "ProposedDelta(" + resource + ')'; //$NON-NLS-1$ + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java new file mode 100644 index 0000000000..56e6d9b86d --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * 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.resources.mapping; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.runtime.IAdapterFactory; + +/** + * Adapter factory converting IResource to ResourceMapping + * + * @since 3.1 + */ +public class ResourceAdapterFactory implements IAdapterFactory { + @Override + @SuppressWarnings("unchecked") + public T getAdapter(Object adaptableObject, Class adapterType) { + if (adapterType == ResourceMapping.class && adaptableObject instanceof IResource) { + return (T) new SimpleResourceMapping((IResource) adaptableObject); + } + return null; + } + + @Override + public Class[] getAdapterList() { + return new Class[] {ResourceMapping.class}; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java new file mode 100644 index 0000000000..5b09ff6f73 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java @@ -0,0 +1,261 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.mapping; + +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; + +/** + * Factory for creating a resource delta that describes a proposed change. + */ +public class ResourceChangeDescriptionFactory implements IResourceChangeDescriptionFactory { + private ProposedResourceDelta root = new ProposedResourceDelta(ResourcesPlugin.getWorkspace().getRoot()); + + /** + * Creates and a delta representing a deleted resource, and adds it to the provided + * parent delta. + * @param parentDelta The parent of the deletion delta to create + * @param resource The deleted resource to create a delta for + */ + private ProposedResourceDelta buildDeleteDelta(ProposedResourceDelta parentDelta, IResource resource) { + //start with the existing delta for this resource, if any, to preserve other flags + ProposedResourceDelta delta = parentDelta.getChild(resource.getName()); + if (delta == null) { + delta = new ProposedResourceDelta(resource); + parentDelta.add(delta); + } + delta.setKind(IResourceDelta.REMOVED); + if (resource.getType() == IResource.FILE) + return delta; + //recurse to build deletion deltas for children + try { + IResource[] members = ((IContainer) resource).members(); + int childCount = members.length; + if (childCount > 0) { + ProposedResourceDelta[] childDeltas = new ProposedResourceDelta[childCount]; + for (int i = 0; i < childCount; i++) + childDeltas[i] = buildDeleteDelta(delta, members[i]); + } + } catch (CoreException e) { + //don't need to create deletion deltas for children of inaccessible resources + } + return delta; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.IProposedResourceDeltaFactory#change(org.eclipse.core.resources.IFile) + */ + @Override + public void change(IFile file) { + ProposedResourceDelta delta = getDelta(file); + if (delta.getKind() == 0) + delta.setKind(IResourceDelta.CHANGED); + //the CONTENT flag only applies to the changed and moved from cases + if (delta.getKind() == IResourceDelta.CHANGED || (delta.getFlags() & IResourceDelta.MOVED_FROM) != 0 || (delta.getFlags() & IResourceDelta.COPIED_FROM) != 0) + delta.addFlags(IResourceDelta.CONTENT); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.IProposedResourceDeltaFactory#close(org.eclipse.core.resources.IProject) + */ + @Override + public void close(IProject project) { + delete(project); + ProposedResourceDelta delta = getDelta(project); + delta.addFlags(IResourceDelta.OPEN); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.IProposedResourceDeltaFactory#copy(org.eclipse.core.resources.IResource, org.eclipse.core.runtime.IPath) + */ + @Override + public void copy(IResource resource, IPath destination) { + moveOrCopyDeep(resource, destination, false /* copy */); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory#create(org.eclipse.core.resources.IResource) + */ + @Override + public void create(IResource resource) { + getDelta(resource).setKind(IResourceDelta.ADDED); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.IProposedResourceDeltaFactory#delete(org.eclipse.core.resources.IResource) + */ + @Override + public void delete(IResource resource) { + if (resource.getType() == IResource.ROOT) { + //the root itself cannot be deleted, so create deletions for each project + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + buildDeleteDelta(root, projects[i]); + } else { + buildDeleteDelta(getDelta(resource.getParent()), resource); + } + } + + private void fail(CoreException e) { + Policy.log(e.getStatus().getSeverity(), "An internal error occurred while accumulating a change description.", e); //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.IProposedResourceDeltaFactory#getDelta() + */ + @Override + public IResourceDelta getDelta() { + return root; + } + + ProposedResourceDelta getDelta(IResource resource) { + ProposedResourceDelta delta = (ProposedResourceDelta) root.findMember(resource.getFullPath()); + if (delta != null) { + return delta; + } + ProposedResourceDelta parent = getDelta(resource.getParent()); + delta = new ProposedResourceDelta(resource); + parent.add(delta); + return delta; + } + + /* + * Return the resource at the destination path that corresponds to the source resource + * @param source the source resource + * @param sourcePrefix the path of the root of the move or copy + * @param destinationPrefix the path of the destination the root was copied to + * @return the destination resource + */ + protected IResource getDestinationResource(IResource source, IPath sourcePrefix, IPath destinationPrefix) { + IPath relativePath = source.getFullPath().removeFirstSegments(sourcePrefix.segmentCount()); + IPath destinationPath = destinationPrefix.append(relativePath); + IResource destination; + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); + switch (source.getType()) { + case IResource.FILE : + destination = wsRoot.getFile(destinationPath); + break; + case IResource.FOLDER : + destination = wsRoot.getFolder(destinationPath); + break; + case IResource.PROJECT : + destination = wsRoot.getProject(destinationPath.segment(0)); + break; + default : + // Shouldn't happen + destination = null; + } + return destination; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.IProposedResourceDeltaFactory#move(org.eclipse.core.resources.IResource, org.eclipse.core.runtime.IPath) + */ + @Override + public void move(IResource resource, IPath destination) { + moveOrCopyDeep(resource, destination, true /* move */); + } + + /** + * Builds the delta representing a single resource being moved or copied. + * + * @param resource The resource being moved + * @param sourcePrefix The root of the sub-tree being moved + * @param destinationPrefix The root of the destination sub-tree + * @param move true for a move, false for a copy + * @return Whether to move or copy the child + */ + boolean moveOrCopy(IResource resource, final IPath sourcePrefix, final IPath destinationPrefix, final boolean move) { + ProposedResourceDelta sourceDelta = getDelta(resource); + if (sourceDelta.getKind() == IResourceDelta.REMOVED) { + // There is already a removed delta here so there + // is nothing to move/copy + return false; + } + IResource destinationResource = getDestinationResource(resource, sourcePrefix, destinationPrefix); + ProposedResourceDelta destinationDelta = getDelta(destinationResource); + if ((destinationDelta.getKind() & (IResourceDelta.ADDED | IResourceDelta.CHANGED)) > 0) { + // There is already a resource at the destination + // TODO: What do we do + return false; + } + // First, create the delta for the source + IPath fromPath = resource.getFullPath(); + boolean wasAdded = false; + final int sourceFlags = sourceDelta.getFlags(); + if (move) { + // We transfer the source flags to the destination + if (sourceDelta.getKind() == IResourceDelta.ADDED) { + if ((sourceFlags & IResourceDelta.MOVED_FROM) != 0) { + // The resource was moved from somewhere else so + // we need to transfer the path to the new location + fromPath = sourceDelta.getMovedFromPath(); + sourceDelta.setMovedFromPath(null); + } + // The source was added and then moved so we'll + // make it an add at the destination + sourceDelta.setKind(0); + wasAdded = true; + } else { + // We reset the status to be a remove/move_to + sourceDelta.setKind(IResourceDelta.REMOVED); + sourceDelta.setFlags(IResourceDelta.MOVED_TO); + sourceDelta.setMovedToPath(destinationPrefix.append(fromPath.removeFirstSegments(sourcePrefix.segmentCount()))); + } + } + // Next, create the delta for the destination + if (destinationDelta.getKind() == IResourceDelta.REMOVED) { + // The destination was removed and is being re-added + destinationDelta.setKind(IResourceDelta.CHANGED); + destinationDelta.addFlags(IResourceDelta.REPLACED); + } else { + destinationDelta.setKind(IResourceDelta.ADDED); + } + if (!wasAdded || !fromPath.equals(resource.getFullPath())) { + // The source wasn't added so it is a move/copy + destinationDelta.addFlags(move ? IResourceDelta.MOVED_FROM : IResourceDelta.COPIED_FROM); + destinationDelta.setMovedFromPath(fromPath); + // Apply the source flags + if (move) + destinationDelta.addFlags(sourceFlags); + } + + return true; + } + + /** + * Helper method that generate a move or copy delta for a sub-tree + * of resources being moved or copied. + */ + private void moveOrCopyDeep(IResource resource, IPath destination, final boolean move) { + final IPath sourcePrefix = resource.getFullPath(); + final IPath destinationPrefix = destination; + try { + //build delta for the entire sub-tree if available + if (resource.isAccessible()) { + resource.accept(new IResourceVisitor() { + @Override + public boolean visit(IResource child) { + return moveOrCopy(child, sourcePrefix, destinationPrefix, move); + } + }); + } else { + //just build a delta for the single resource + moveOrCopy(resource, sourcePrefix, destination, move); + } + } catch (CoreException e) { + fail(e); + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java new file mode 100644 index 0000000000..f236956a36 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.*; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.*; + +/** + * A simple model provider that represents the resource model itself. + * + * @since 3.2 + */ +public final class ResourceModelProvider extends ModelProvider { + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.ModelProvider#getMappings(org.eclipse.core.resources.IResource, org.eclipse.core.resources.mapping.ResourceMappingContext, org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public ResourceMapping[] getMappings(IResource resource, ResourceMappingContext context, IProgressMonitor monitor) { + return new ResourceMapping[] {new SimpleResourceMapping(resource)}; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.ModelProvider#getMappings(org.eclipse.core.resources.mapping.ResourceTraversal[], org.eclipse.core.resources.mapping.ResourceMappingContext, org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public ResourceMapping[] getMappings(ResourceTraversal[] traversals, ResourceMappingContext context, IProgressMonitor monitor) { + Set result = new HashSet(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + IResource[] resources = traversal.getResources(); + int depth = traversal.getDepth(); + for (int j = 0; j < resources.length; j++) { + IResource resource = resources[j]; + switch (depth) { + case IResource.DEPTH_INFINITE : + result.add(resource); + break; + case IResource.DEPTH_ONE : + if (resource.getType() == IResource.FILE) { + result.add(resource); + } else { + result.add(new ShallowContainer((IContainer)resource)); + } + break; + case IResource.DEPTH_ZERO : + if (resource.getType() == IResource.FILE) + result.add(resource); + break; + } + } + } + ResourceMapping[] mappings = new ResourceMapping[result.size()]; + int i = 0; + for (Iterator iter = result.iterator(); iter.hasNext();) { + Object element = iter.next(); + if (element instanceof IResource) { + mappings[i++] = new SimpleResourceMapping((IResource) element); + } else { + mappings[i++] = new ShallowResourceMapping((ShallowContainer)element); + } + } + return mappings; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java new file mode 100644 index 0000000000..4fa81f0e70 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.mapping; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.PlatformObject; + +/** + * A special model object used to represent shallow folders + */ +public class ShallowContainer extends PlatformObject { + + private IContainer container; + + public ShallowContainer(IContainer container) { + this.container = container; + } + + public IContainer getResource() { + return container; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj instanceof ShallowContainer) { + ShallowContainer other = (ShallowContainer) obj; + return other.getResource().equals(getResource()); + } + return false; + } + + @Override + public int hashCode() { + return getResource().hashCode(); + } + + @Override + @SuppressWarnings("unchecked") + public T getAdapter(Class adapter) { + if (adapter == IResource.class || adapter == IContainer.class) + return (T) container; + return super.getAdapter(adapter); + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java new file mode 100644 index 0000000000..15f046dbf4 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.mapping; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A resource mapping for a shallow container. + */ +public class ShallowResourceMapping extends ResourceMapping { + + private final ShallowContainer container; + + public ShallowResourceMapping(ShallowContainer container) { + this.container = container; + } + + @Override + public Object getModelObject() { + return container; + } + + @Override + public String getModelProviderId() { + return ModelProvider.RESOURCE_MODEL_PROVIDER_ID; + } + + @Override + public IProject[] getProjects() { + return new IProject[] { container.getResource().getProject() }; + } + + @Override + public ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) { + return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { container.getResource() }, IResource.DEPTH_ONE, IResource.NONE)}; + } + + @Override + public boolean contains(ResourceMapping mapping) { + if (mapping.getModelProviderId().equals(this.getModelProviderId())) { + Object object = mapping.getModelObject(); + IResource resource = container.getResource(); + // A shallow mapping only contains direct file children or equal shallow containers + if (object instanceof ShallowContainer) { + ShallowContainer sc = (ShallowContainer) object; + return sc.getResource().equals(resource); + } + if (object instanceof IResource) { + IResource other = (IResource) object; + return other.getType() == IResource.FILE + && resource.getFullPath().equals(other.getFullPath().removeLastSegments(1)); + } + } + return false; + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java new file mode 100644 index 0000000000..c9b7bb8eef --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * 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.resources.mapping; + +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A simple resource mapping for converting IResource to ResourceMapping. + * It uses the resource as the model object and traverses deeply. + * + * @since 3.1 + */ +public class SimpleResourceMapping extends ResourceMapping { + private final IResource resource; + + public SimpleResourceMapping(IResource resource) { + this.resource = resource; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.ResourceMapping#contains(org.eclipse.core.resources.mapping.ResourceMapping) + */ + @Override + public boolean contains(ResourceMapping mapping) { + if (mapping.getModelProviderId().equals(this.getModelProviderId())) { + Object object = mapping.getModelObject(); + if (object instanceof IResource) { + IResource other = (IResource) object; + return resource.getFullPath().isPrefixOf(other.getFullPath()); + } + if (object instanceof ShallowContainer) { + ShallowContainer sc = (ShallowContainer) object; + IResource other = sc.getResource(); + return resource.getFullPath().isPrefixOf(other.getFullPath()); + } + } + return false; + } + + /* (non-Javadoc) + * Method declared on ResourceMapping. + */ + @Override + public Object getModelObject() { + return resource; + } + + @Override + public String getModelProviderId() { + return ModelProvider.RESOURCE_MODEL_PROVIDER_ID; + } + + /* (non-Javadoc) + * Method declared on ResourceMapping. + */ + @Override + public IProject[] getProjects() { + if (resource.getType() == IResource.ROOT) + return ((IWorkspaceRoot)resource).getProjects(); + return new IProject[] {resource.getProject()}; + } + + /* (non-Javadoc) + * Method declared on ResourceMapping. + */ + @Override + public ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) { + if (resource.getType() == IResource.ROOT) { + return new ResourceTraversal[] {new ResourceTraversal(((IWorkspaceRoot)resource).getProjects(), IResource.DEPTH_INFINITE, IResource.NONE)}; + } + return new ResourceTraversal[] {new ResourceTraversal(new IResource[] {resource}, IResource.DEPTH_INFINITE, IResource.NONE)}; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java new file mode 100644 index 0000000000..0132b522f9 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import java.net.URISyntaxException; +import java.net.URL; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.URIUtil; + +/** + * ECLIPSE_HOME project variable, pointing to the location of the eclipse install directory. + * + */ +public class EclipseHomeProjectVariable extends PathVariableResolver { + + public static String NAME = "ECLIPSE_HOME"; //$NON-NLS-1$ + + public EclipseHomeProjectVariable() { + // nothing to do. + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + URL installURL = Platform.getInstallLocation().getURL(); + try { + return URIUtil.toURI(installURL).toASCIIString(); + } catch (URISyntaxException e) { + return null; + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java new file mode 100644 index 0000000000..1af8f974e4 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import java.net.URI; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * Path Variable representing the parent directory of the variable provided + * in argument, following the syntax: + * + * "${PARENT-COUNT-MyVariable}" + * + */ +public class ParentVariableResolver extends PathVariableResolver { + + final public static String NAME = "PARENT"; //$NON-NLS-1$ + + public ParentVariableResolver() { + // nothing + } + + @Override + public String getValue(String variable, IResource resource) { + int index = variable.indexOf('-'); + if (index == -1 || index == (variable.length() -1)) + return null; + + String countRemaining = variable.substring(index + 1); + index = countRemaining.indexOf('-'); + if (index == -1 || index == (variable.length() -1)) + return null; + + String countString = countRemaining.substring(0, index); + int count = 0; + try { + count = Integer.parseInt(countString); + if (count < 0) + return null; + }catch (NumberFormatException e) { + return null; + } + String argument = countRemaining.substring(index + 1); + + URI value = resource.getPathVariableManager().getURIValue(argument); + if (value == null) + return null; + value = resource.getPathVariableManager().resolveURI(value); + value = URIUtil.toURI(URIUtil.toPath(value).removeLastSegments(count)); + + return value.toASCIIString(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java new file mode 100644 index 0000000000..293e8026ff --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * + */ +public class ProjectLocationVariableResolver extends PathVariableResolver { + + public static String NAME = "PROJECT_LOC"; //$NON-NLS-1$ + + public ProjectLocationVariableResolver() { + // nothing + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + if (resource.getProject().getLocationURI() != null) + return resource.getProject().getLocationURI().toASCIIString(); + return null; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java new file mode 100644 index 0000000000..fe12993d6b --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * + */ +public class WorkspaceLocationVariableResolver extends PathVariableResolver { + + public static String NAME = "WORKSPACE_LOC"; //$NON-NLS-1$ + + public WorkspaceLocationVariableResolver() { + // nothing to do + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + return resource.getWorkspace().getRoot().getLocationURI().toASCIIString(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java new file mode 100644 index 0000000000..dae08f6bcf --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import java.net.URI; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * Returns the location of the parent resource + */ +public class WorkspaceParentLocationVariableResolver extends PathVariableResolver { + + public static String NAME = "PARENT_LOC"; //$NON-NLS-1$ + + public WorkspaceParentLocationVariableResolver() { + // nothing + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + IContainer parent = resource.getParent(); + if (parent != null) { + URI locationURI = parent.getLocationURI(); + if (locationURI != null) + return locationURI.toASCIIString(); + } + return null; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java new file mode 100644 index 0000000000..6910d7fe74 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2002, 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 - Initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.resources.refresh.win32; + +import java.io.*; + +/** + * Performs character to byte conversion for passing strings to native win32 + * methods. + */ +public class Convert { + + /* + * Obtains the default encoding on this platform + */ + private static String defaultEncoding= + new InputStreamReader(new ByteArrayInputStream(new byte[0])).getEncoding(); + + /** + * Converts the given String to bytes using the platforms default + * encoding. + * + * @param target The String to be converted, can not be null. + * @return byte[] The resulting bytes, or null. + */ + /** + * Calling String.getBytes() creates a new encoding object and other garbage. + * This can be avoided by calling String.getBytes(String encoding) instead. + */ + public static byte[] toPlatformBytes(String target) { + if (defaultEncoding == null) + return target.getBytes(); + // try to use the default encoding + try { + return target.getBytes(defaultEncoding); + } catch (UnsupportedEncodingException e) { + // null the default encoding so we don't try it again + defaultEncoding = null; + return target.getBytes(); + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java new file mode 100644 index 0000000000..223c5b88fb --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java @@ -0,0 +1,607 @@ +/******************************************************************************* + * Copyright (c) 2002, 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.resources.refresh.win32; + +import java.io.File; +import java.util.*; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.refresh.IRefreshMonitor; +import org.eclipse.core.resources.refresh.IRefreshResult; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.Bundle; + +/** + * A monitor that works on Win32 platforms. Provides simple notification of + * entire trees by reporting that the root of the tree has changed to depth + * DEPTH_INFINITE. + */ +class Win32Monitor extends Job implements IRefreshMonitor { + /** + * The delay between invocations of the refresh job. + */ + private static final long RESCHEDULE_DELAY = 3000; + /** + * The time to wait on blocking call to native refresh hook. + */ + private static final int WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT = 1000; + private static final String DEBUG_PREFIX = "Win32RefreshMonitor: "; //$NON-NLS-1$ + + /** + * A ChainedHandle is a linked list of handles. + */ + protected abstract class ChainedHandle extends Handle { + private ChainedHandle next; + private ChainedHandle previous; + + public abstract boolean exists(); + + public ChainedHandle getNext() { + return next; + } + + public ChainedHandle getPrevious() { + return previous; + } + + public void setNext(ChainedHandle next) { + this.next = next; + } + + public void setPrevious(ChainedHandle previous) { + this.previous = previous; + } + } + + protected class FileHandle extends ChainedHandle { + private File file; + + public FileHandle(File file) { + this.file = file; + } + + @Override + public boolean exists() { + return file.exists(); + } + + @Override + public void handleNotification() { + if (!isOpen()) + return; + ChainedHandle next = getNext(); + if (next != null) { + if (next.isOpen()) { + if (!next.exists()) { + if (next instanceof LinkedResourceHandle) { + next.close(); + LinkedResourceHandle linkedResourceHandle = (LinkedResourceHandle) next; + linkedResourceHandle.postRefreshRequest(); + } else { + next.close(); + } + ChainedHandle previous = getPrevious(); + if (previous != null) + previous.open(); + } + } else { + next.open(); + if (next.isOpen()) { + Handle previous = getPrevious(); + previous.close(); + if (next instanceof LinkedResourceHandle) + ((LinkedResourceHandle) next).postRefreshRequest(); + } + } + } + findNextChange(); + } + + @Override + public void open() { + if (!isOpen()) { + Handle next = getNext(); + if (next != null && next.isOpen()) { + openHandleOn(file); + } else { + if (exists()) { + openHandleOn(file); + } + Handle previous = getPrevious(); + if (previous != null) { + previous.open(); + } + } + } + } + } + + protected abstract class Handle { + protected long handleValue; + + public Handle() { + handleValue = Win32Natives.INVALID_HANDLE_VALUE; + } + + public void close() { + if (isOpen()) { + if (!Win32Natives.FindCloseChangeNotification(handleValue)) { + int error = Win32Natives.GetLastError(); + if (error != Win32Natives.ERROR_INVALID_HANDLE) + addException(NLS.bind(Messages.WM_errCloseHandle, Integer.toString(error))); + } + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "removed handle: " + handleValue); //$NON-NLS-1$ + handleValue = Win32Natives.INVALID_HANDLE_VALUE; + } + } + + private long createHandleValue(String path, boolean monitorSubtree, int flags) { + long handle = Win32Natives.FindFirstChangeNotification(path, monitorSubtree, flags); + if (handle == Win32Natives.INVALID_HANDLE_VALUE) { + int error = Win32Natives.GetLastError(); + addException(NLS.bind(Messages.WM_errCreateHandle, path, Integer.toString(error))); + } + return handle; + } + + public void destroy() { + close(); + } + + protected void findNextChange() { + if (!Win32Natives.FindNextChangeNotification(handleValue)) { + int error = Win32Natives.GetLastError(); + if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) { + addException(NLS.bind(Messages.WM_errFindChange, Integer.toString(error))); + } + removeHandle(this); + } + } + + public long getHandleValue() { + return handleValue; + } + + public abstract void handleNotification(); + + public boolean isOpen() { + return handleValue != Win32Natives.INVALID_HANDLE_VALUE; + } + + public abstract void open(); + + protected void openHandleOn(File file) { + openHandleOn(file.getAbsolutePath(), false); + } + + protected void openHandleOn(IResource resource) { + openHandleOn(resource.getLocation().toOSString(), true); + } + + private void openHandleOn(String path, boolean subtree) { + setHandleValue(createHandleValue(path, subtree, Win32Natives.FILE_NOTIFY_CHANGE_FILE_NAME | Win32Natives.FILE_NOTIFY_CHANGE_DIR_NAME | Win32Natives.FILE_NOTIFY_CHANGE_LAST_WRITE | Win32Natives.FILE_NOTIFY_CHANGE_SIZE)); + if (isOpen()) { + fHandleValueToHandle.put(new Long(getHandleValue()), this); + setHandleValueArrays(createHandleArrays()); + } else { + close(); + } + } + + protected void postRefreshRequest(IResource resource) { + //native callback occurs even if resource was changed within workspace + if (!resource.isSynchronized(IResource.DEPTH_INFINITE)) + refreshResult.refresh(resource); + } + + public void setHandleValue(long handleValue) { + this.handleValue = handleValue; + } + } + + protected class LinkedResourceHandle extends ChainedHandle { + private List fileHandleChain; + private IResource resource; + + /** + * @param resource + */ + public LinkedResourceHandle(IResource resource) { + this.resource = resource; + createFileHandleChain(); + } + + protected void createFileHandleChain() { + fileHandleChain = new ArrayList(1); + File file = new File(resource.getLocation().toOSString()); + file = file.getParentFile(); + while (file != null) { + fileHandleChain.add(0, new FileHandle(file)); + file = file.getParentFile(); + } + int size = fileHandleChain.size(); + for (int i = 0; i < size; i++) { + ChainedHandle handle = fileHandleChain.get(i); + handle.setPrevious((i > 0) ? fileHandleChain.get(i - 1) : null); + handle.setNext((i + 1 < size) ? fileHandleChain.get(i + 1) : this); + } + setPrevious((size > 0) ? fileHandleChain.get(size - 1) : null); + } + + @Override + public void destroy() { + super.destroy(); + for (Iterator i = fileHandleChain.iterator(); i.hasNext();) { + Handle handle = i.next(); + handle.destroy(); + } + } + + @Override + public boolean exists() { + IPath location = resource.getLocation(); + return location == null ? false : location.toFile().exists(); + } + + @Override + public void handleNotification() { + if (isOpen()) { + postRefreshRequest(resource); + findNextChange(); + } + } + + @Override + public void open() { + if (!isOpen()) { + if (exists()) { + openHandleOn(resource); + } + FileHandle handle = (FileHandle) getPrevious(); + if (handle != null && !handle.isOpen()) { + handle.open(); + } + } + } + + public void postRefreshRequest() { + postRefreshRequest(resource); + } + } + + protected class ResourceHandle extends Handle { + private IResource resource; + + public ResourceHandle(IResource resource) { + super(); + this.resource = resource; + } + + public IResource getResource() { + return resource; + } + + @Override + public void handleNotification() { + if (isOpen()) { + postRefreshRequest(resource); + findNextChange(); + } + } + + @Override + public void open() { + if (!isOpen()) { + openHandleOn(resource); + } + } + } + + /** + * Any errors that have occurred + */ + protected MultiStatus errors; + /** + * Arrays of handles, split evenly when the number of handles is larger + * than Win32Natives.MAXIMUM_WAIT_OBJECTS + */ + protected long[][] fHandleValueArrays; + /** + * Mapping of handles (java.lang.Long) to absolute paths + * (java.lang.String). + */ + protected Map fHandleValueToHandle; + protected IRefreshResult refreshResult; + + /* + * Creates a new monitor. @param result A result that will receive refresh + * callbacks and error notifications + */ + public Win32Monitor(IRefreshResult result) { + super(Messages.WM_jobName); + this.refreshResult = result; + setPriority(Job.DECORATE); + setSystem(true); + fHandleValueToHandle = new HashMap(1); + setHandleValueArrays(createHandleArrays()); + } + + /** + * Logs an exception + */ + protected synchronized void addException(String message) { + if (errors == null) { + String msg = Messages.WM_errors; + errors = new MultiStatus(ResourcesPlugin.PI_RESOURCES, 1, msg, null); + } + errors.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, message, null)); + } + + /* + * Splits the given array into arrays of length no greater than max + * . The lengths of the sub arrays differ in size by no more than + * one element.

Examples:

  • If an array of size 11 is split + * with a max of 4, the resulting arrays are of size 4, 4, and 3.
  • + *
  • If an array of size 18 is split with a max of 5, the resulting + * arrays are of size 5, 5, 4, and 4.
+ */ + private long[][] balancedSplit(final long[] array, final int max) { + int elementCount = array.length; + // want to handle [1, max] rather than [0, max) + int subArrayCount = ((elementCount - 1) / max) + 1; + int subArrayBaseLength = elementCount / subArrayCount; + int overflow = elementCount % subArrayCount; + long[][] result = new long[subArrayCount][]; + int count = 0; + for (int i = 0; i < subArrayCount; i++) { + int subArrayLength = subArrayBaseLength + (overflow-- > 0 ? 1 : 0); + long[] subArray = new long[subArrayLength]; + for (int j = 0; j < subArrayLength; j++) { + subArray[j] = array[count++]; + } + result[i] = subArray; + } + return result; + } + + private Handle createHandle(IResource resource) { + if (resource.isLinked()) + return new LinkedResourceHandle(resource); + return new ResourceHandle(resource); + } + + /* + * Since the Win32Natives.WaitForMultipleObjects(...) method cannot accept + * more than a certain number of objects, we are forced to split the array + * of objects to monitor and monitor each one individually.

This method + * splits the list of handles into arrays no larger than + * Win32Natives.MAXIMUM_WAIT_OBJECTS. The arrays are balanced so that they + * differ in size by no more than one element. + */ + protected long[][] createHandleArrays() { + long[] handles; + // synchronized: in order to protect the map during iteration + synchronized (fHandleValueToHandle) { + Set keys = fHandleValueToHandle.keySet(); + int size = keys.size(); + if (size == 0) { + return new long[0][0]; + } + handles = new long[size]; + int count = 0; + for (Iterator i = keys.iterator(); i.hasNext();) { + handles[count++] = i.next().longValue(); + } + } + return balancedSplit(handles, Win32Natives.MAXIMUM_WAIT_OBJECTS); + } + + private Handle getHandle(IResource resource) { + if (resource == null) { + return null; + } + // synchronized: in order to protect the map during iteration + synchronized (fHandleValueToHandle) { + for (Iterator i = fHandleValueToHandle.values().iterator(); i.hasNext();) { + Handle handle = i.next(); + if (handle instanceof ResourceHandle) { + ResourceHandle resourceHandle = (ResourceHandle) handle; + if (resourceHandle.getResource().equals(resource)) { + return handle; + } + } + } + } + return null; + } + + /* + * Answers arrays of handles. The handles are split evenly when the number + * of handles becomes larger than Win32Natives.MAXIMUM_WAIT_OBJECTS. + * @return long[][] + */ + private long[][] getHandleValueArrays() { + return fHandleValueArrays; + } + + /** + * Adds a resource to be monitored by this native monitor + */ + public boolean monitor(IResource resource) { + IPath location = resource.getLocation(); + if (location == null) { + // cannot monitor remotely managed containers + return false; + } + Handle handle = createHandle(resource); + // synchronized: handle creation must be atomic + synchronized (this) { + handle.open(); + } + if (!handle.isOpen()) { + //ignore errors if we can't even create a handle on the resource + //it will fall back to polling anyway + errors = null; + return false; + } + //make sure the job is running + schedule(RESCHEDULE_DELAY); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + " added monitor for: " + resource); //$NON-NLS-1$ + return true; + } + + /** + * Removes the handle from the fHandleValueToHandle map. + * + * @param handle + * a handle, not null + */ + protected void removeHandle(Handle handle) { + List handles = new ArrayList(1); + handles.add(handle); + removeHandles(handles); + } + + /** + * Removes all of the handles in the given collection from the fHandleValueToHandle + * map. If collections from the fHandleValueToHandle map are + * used, copy them before passing them in as this method modifies the + * fHandleValueToHandle map. + * + * @param handles + * a collection of handles, not null + */ + private void removeHandles(Collection handles) { + // synchronized: protect the array, removal must be atomic + synchronized (this) { + for (Iterator i = handles.iterator(); i.hasNext();) { + Handle handle = i.next(); + fHandleValueToHandle.remove(new Long(handle.getHandleValue())); + handle.destroy(); + } + setHandleValueArrays(createHandleArrays()); + } + } + + /* + * @see java.lang.Runnable#run() + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + long start = -System.currentTimeMillis(); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "job started."); //$NON-NLS-1$ + try { + long[][] handleArrays = getHandleValueArrays(); + monitor.beginTask(Messages.WM_beginTask, handleArrays.length); + // If changes occur to the list of handles, + // ignore them until the next time through the loop. + for (int i = 0, length = handleArrays.length; i < length; i++) { + if (monitor.isCanceled()) + return Status.CANCEL_STATUS; + waitForNotification(handleArrays[i]); + monitor.worked(1); + } + } finally { + monitor.done(); + start += System.currentTimeMillis(); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "job finished in: " + start + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + //always reschedule the job - so it will come back after errors or cancelation + long delay = Math.max(RESCHEDULE_DELAY, start); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "rescheduling in: " + delay / 1000 + " seconds"); //$NON-NLS-1$ //$NON-NLS-2$ + final Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES); + //if the bundle is null then the framework has shutdown - just bail out completely (bug 98219) + if (bundle == null) + return Status.OK_STATUS; + //don't reschedule the job if the resources plugin has been shut down + if (bundle.getState() == Bundle.ACTIVE) + schedule(delay); + MultiStatus result = errors; + errors = null; + //just log native refresh failures + if (result != null && !result.isOK()) + ResourcesPlugin.getPlugin().getLog().log(result); + return Status.OK_STATUS; + } + + protected void setHandleValueArrays(long[][] arrays) { + fHandleValueArrays = arrays; + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.Job#shouldRun() + */ + @Override + public boolean shouldRun() { + return !fHandleValueToHandle.isEmpty(); + } + + /* + * @see org.eclipse.core.resources.refresh.IRefreshMonitor#unmonitor(IContainer) + */ + @Override + public void unmonitor(IResource resource) { + if (resource == null) { + // resource == null means stop monitoring all resources + synchronized (fHandleValueToHandle) { + removeHandles(new ArrayList(fHandleValueToHandle.values())); + } + } else { + Handle handle = getHandle(resource); + if (handle != null) + removeHandle(handle); + } + //stop the job if there are no more handles + if (fHandleValueToHandle.isEmpty()) + cancel(); + } + + /** + * Performs the native call to wait for notification on one of the given + * handles. + * + * @param handleValues + * an array of handles, it must contain no duplicates. + */ + private void waitForNotification(long[] handleValues) { + int handleCount = handleValues.length; + int index = Win32Natives.WaitForMultipleObjects(handleCount, handleValues, false, WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT); + if (index == Win32Natives.WAIT_TIMEOUT) { + // nothing happened. + return; + } + if (index == Win32Natives.WAIT_FAILED) { + // we ran into a problem + int error = Win32Natives.GetLastError(); + if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) { + addException(NLS.bind(Messages.WM_nativeErr, Integer.toString(error))); + refreshResult.monitorFailed(this, null); + } + return; + } + // a change occurred + // WaitForMultipleObjects returns WAIT_OBJECT_0 + index + index -= Win32Natives.WAIT_OBJECT_0; + Handle handle = fHandleValueToHandle.get(new Long(handleValues[index])); + if (handle != null) + handle.handleNotification(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java new file mode 100644 index 0000000000..4aa43ec5de --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java @@ -0,0 +1,347 @@ +/******************************************************************************* + * Copyright (c) 2002, 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 - Initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.resources.refresh.win32; + + +/** + * Hooks for native methods involved with win32 auto-refresh callbacks. + */ +public class Win32Natives { + /* general purpose */ + /** + * A general use constant expressing the value of an + * invalid handle. + */ + public static final long INVALID_HANDLE_VALUE; + /** + * An error constant which indicates that the previous function + * succeeded. + */ + public static final int ERROR_SUCCESS; + /** + * An error constant which indicates that a handle is or has become + * invalid. + */ + public static final int ERROR_INVALID_HANDLE; + /** + * The combination of all of the error constants. + */ + public static int FILE_NOTIFY_ALL; + /** + * A constant which indicates the maximum number of objects + * that can be passed into WaitForMultipleObjects. + */ + public static final int MAXIMUM_WAIT_OBJECTS; + /** + * A constant which indicates the maximum length of a pathname. + */ + public static final int MAX_PATH; + /** + * A constant which expresses the concept of the infinite. + */ + public static final int INFINITE; + + /* wait return values */ + /** + * A constant used returned WaitForMultipleObjects when the function times out. + */ + public static final int WAIT_TIMEOUT; + /** + * A constant used by WaitForMultipleObjects to indicate the object which was + * signaled. + */ + public static final int WAIT_OBJECT_0; + /** + * A constant returned by WaitForMultipleObjects which indicates + * that the wait failed. + */ + public static final int WAIT_FAILED; + + /* wait notification filter masks */ + /** + * Change filter for monitoring file rename, creation or deletion. + */ + public static final int FILE_NOTIFY_CHANGE_FILE_NAME; + /** + * Change filter for monitoring directory creation or deletion. + */ + public static final int FILE_NOTIFY_CHANGE_DIR_NAME; + /** + * Change filter for monitoring file/directory attribute changes. + */ + public static final int FILE_NOTIFY_CHANGE_ATTRIBUTES; + /** + * Change filter for monitoring file size changes. + */ + public static final int FILE_NOTIFY_CHANGE_SIZE; + /** + * Change filter for monitoring the file write timestamp + */ + public static final int FILE_NOTIFY_CHANGE_LAST_WRITE; + /** + * Change filter for monitoring the security descriptors + * of files. + */ + public static final int FILE_NOTIFY_CHANGE_SECURITY; + + /** + * Flag indicating whether or not the OS supports unicode calls. + */ + public static final boolean UNICODE; + /* + * Make requests to set the constants. + */ + static { + System.loadLibrary("win32refresh"); //$NON-NLS-1$ + UNICODE= IsUnicode(); + INVALID_HANDLE_VALUE= INVALID_HANDLE_VALUE(); + ERROR_SUCCESS= ERROR_SUCCESS(); + ERROR_INVALID_HANDLE= ERROR_INVALID_HANDLE(); + + MAXIMUM_WAIT_OBJECTS= MAXIMUM_WAIT_OBJECTS(); + MAX_PATH= MAX_PATH(); + INFINITE= INFINITE(); + + WAIT_TIMEOUT= WAIT_TIMEOUT(); + WAIT_OBJECT_0= WAIT_OBJECT_0(); + WAIT_FAILED= WAIT_FAILED(); + + FILE_NOTIFY_CHANGE_FILE_NAME= FILE_NOTIFY_CHANGE_FILE_NAME(); + FILE_NOTIFY_CHANGE_DIR_NAME= FILE_NOTIFY_CHANGE_DIR_NAME(); + FILE_NOTIFY_CHANGE_ATTRIBUTES= FILE_NOTIFY_CHANGE_ATTRIBUTES(); + FILE_NOTIFY_CHANGE_SIZE= FILE_NOTIFY_CHANGE_SIZE(); + FILE_NOTIFY_CHANGE_LAST_WRITE= FILE_NOTIFY_CHANGE_LAST_WRITE(); + FILE_NOTIFY_CHANGE_SECURITY= FILE_NOTIFY_CHANGE_SECURITY(); + FILE_NOTIFY_ALL= + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_ATTRIBUTES | + FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE | + FILE_NOTIFY_CHANGE_SECURITY; + } + + /** + * Creates a change notification object for the given path. The notification + * object allows the client to monitor changes to the directory and the + * subtree under the directory using FindNextChangeNotification or + * WaitForMultipleObjects. + *

+ * If the OS supports unicode the path must be no longer than 2^15 - 1 characters. + * Otherwise, the path cannot be longer than MAX_PATH. In either case, if the given + * path is too long ERROR_INVALID_HANDLE is returned. + * + * @param lpPathName The path of the file. + * @param bWatchSubtree If true, specifies that the entire + * tree under the given path should be monitored. If false + * specifies that just the named path should be monitored. + * @param dwNotifyFilter Any combination of FILE_NOTIFY_CHANGE_FILE_NAME, + * FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_ATTRIBUTES, + * FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_CHANGE_LAST_WRITE, or + * FILE_NOTIFY_CHANGE_SECURITY. + * @return long The handle to the find change notification object or + * ERROR_INVALID_HANDLE if the attempt fails. + */ + public static long FindFirstChangeNotification(String lpPathName, boolean bWatchSubtree, int dwNotifyFilter) { + if (UNICODE) + return FindFirstChangeNotificationW(lpPathName, bWatchSubtree, dwNotifyFilter); + return FindFirstChangeNotificationA(Convert.toPlatformBytes(lpPathName), bWatchSubtree, dwNotifyFilter); + } + /** + * Creates a change notification object for the given path. This notification object + * allows the client to monitor changes to the directory and the subtree + * under the directory using FindNextChangeNotification or + * WaitForMultipleObjects. + * + * @param lpPathName The path to the directory to be monitored. Cannot be null, + * or longer than 2^15 - 1 characters. + * @param bWatchSubtree If true, specifies that the entire + * tree under the given path should be monitored. If false + * specifies that just the named path should be monitored. + * @param dwNotifyFilter Any combination of FILE_NOTIFY_CHANGE_FILE_NAME, + * FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_ATTRIBUTES, + * FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_CHANGE_LAST_WRITE, or + * FILE_NOTIFY_CHANGE_SECURITY. + * @return long The handle to the find change notification object or + * ERROR_INVALID_HANDLE if the attempt fails. + */ + private static native long FindFirstChangeNotificationW(String lpPathName, boolean bWatchSubtree, int dwNotifyFilter); + + /** + * Creates a change notification object for the given path. This notification object + * allows the client to monitor changes to the directory and the subtree + * under the directory using FindNextChangeNotification or + * WaitForMultipleObjects. + * + * @param lpPathName The path to the directory to be monitored, cannot be null, + * or be longer + * than MAX_PATH. This path must be in platform bytes converted. + * @param bWatchSubtree If true, specifies that the entire + * tree under the given path should be monitored. If false + * specifies that just the named path should be monitored. + * @param dwNotifyFilter Any combination of FILE_NOTIFY_CHANGE_FILE_NAME, + * FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_ATTRIBUTES, + * FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_CHANGE_LAST_WRITE, or + * FILE_NOTIFY_CHANGE_SECURITY. + * @return long The handle to the find change notification object or + * ERROR_INVALID_HANDLE if the attempt fails. + */ + private static native long FindFirstChangeNotificationA(byte[] lpPathName, boolean bWatchSubtree, int dwNotifyFilter); + + + /** + * Stops and disposes of the change notification object that corresponds to the given + * handle. The handle cannot be used in future calls to FindNextChangeNotification or + * WaitForMultipleObjects + * + * @param hChangeHandle a handle which was created with FindFirstChangeNotification + * @return boolean true if the method succeeds, false + * otherwise. + */ + public static native boolean FindCloseChangeNotification(long hChangeHandle); + + /** + * Requests that the next change detected be signaled. This method should only be + * called after FindFirstChangeNotification or WaitForMultipleObjects. Once this + * method has been called on a given handle, further notification requests can be made + * through the WaitForMultipleObjects call. + * @param hChangeHandle a handle which was created with FindFirstChangeNotification + * @return boolean true if the method succeeds, false otherwise. + */ + public static native boolean FindNextChangeNotification(long hChangeHandle); + + /** + * Returns when one of the following occurs. + *

    + *
  • One of the objects is signaled, when bWaitAll is false
  • + *
  • All of the objects are signaled, when bWaitAll is true
  • + *
  • The timeout interval of dwMilliseconds elapses.
  • + *
+ * @param nCount The number of handles, cannot be greater than MAXIMUM_WAIT_OBJECTS. + * @param lpHandles The array of handles to objects to be waited upon cannot contain + * duplicate handles. + * @param bWaitAll If true requires all objects to be signaled before this + * method returns. If false, indicates that only one object need be + * signaled for this method to return. + * @param dwMilliseconds A timeout value in milliseconds. If zero, the function tests + * the objects and returns immediately. If INFINITE, the function will only return + * when the objects have been signaled. + * @return int WAIT_TIMEOUT when the function times out before recieving a signal. + * WAIT_OBJECT_0 + n when a signal for the handle at index n. WAIT_FAILED when this + * function fails. + */ + public static native int WaitForMultipleObjects(int nCount, long[] lpHandles, boolean bWaitAll, int dwMilliseconds); + + /** + * Answers true if the operating system supports + * long filenames. + * @return boolean true if the operating system supports + * long filenames, false otherwise. + */ + private static native boolean IsUnicode(); + + /** + * Answers the last error set in the current thread. + * @return int the last error + */ + public static native int GetLastError(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_LAST_WRITE. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_LAST_WRITE(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_DIR_NAME. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_DIR_NAME(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_ATTRIBUTES. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_ATTRIBUTES(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_SIZE. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_SIZE(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_FILE_NAME. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_FILE_NAME(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_SECURITY. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_SECURITY(); + + /** + * Returns the constant MAXIMUM_WAIT_OBJECTS. + * @return int + */ + private static native int MAXIMUM_WAIT_OBJECTS(); + + /** + * Returns the constant MAX_PATH. + * @return int + */ + private static native int MAX_PATH(); + + /** + * Returns the constant INFINITE. + * @return int + */ + private static native int INFINITE(); + + /** + * Returns the constant WAIT_OBJECT_0. + * @return int + */ + private static native int WAIT_OBJECT_0(); + + /** + * Returns the constant WAIT_FAILED. + * @return int + */ + private static native int WAIT_FAILED(); + + /** + * Returns the constant WAIT_TIMEOUT. + * @return int + */ + private static native int WAIT_TIMEOUT(); + + /** + * Returns the constant ERROR_INVALID_HANDLE. + * @return int + */ + private static native int ERROR_INVALID_HANDLE(); + + /** + * Returns the constant ERROR_SUCCESS. + * @return int + */ + private static native int ERROR_SUCCESS(); + + /** + * Returns the constant INVALID_HANDLE_VALUE. + * @return long + */ + private static native long INVALID_HANDLE_VALUE(); + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java new file mode 100644 index 0000000000..9ba1dade6e --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.internal.resources.refresh.win32; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.refresh.*; + +/** + * The Win32RefreshProvider creates monitors that + * can monitor drives on Win32 platforms. + * + * @see org.eclipse.core.resources.refresh.RefreshProvider + */ +public class Win32RefreshProvider extends RefreshProvider { + private Win32Monitor monitor; + + /** + * Creates a standard Win32 monitor if the given resource is local. + * + * @see org.eclipse.core.resources.refresh.RefreshProvider#installMonitor(IResource,IRefreshResult) + */ + @Override + public IRefreshMonitor installMonitor(IResource resource, IRefreshResult result) { + if (resource.getLocation() == null || !resource.exists() || resource.getType() == IResource.FILE) + return null; + if (monitor == null) + monitor = new Win32Monitor(result); + if (monitor.monitor(resource)) + return monitor; + return null; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java new file mode 100644 index 0000000000..5357447992 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.utils; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * An object that iterates over the elements of an array + */ +public class ArrayIterator implements Iterator { + T[] elements; + int index; + int lastElement; + + /** + * Returns new array enumeration over the given object array + */ + public ArrayIterator(T[] elements) { + this(elements, 0, elements.length - 1); + } + + /** + * Returns new array enumeration over the given object array + */ + public ArrayIterator(T[] elements, int firstElement, int lastElement) { + super(); + this.elements = elements; + index = firstElement; + this.lastElement = lastElement; + } + + /** + * Returns true if this enumeration contains more elements. + */ + @Override + public boolean hasNext() { + return elements != null && index <= lastElement; + } + + /** + * Returns the next element of this enumeration. + * @exception NoSuchElementException if no more elements exist. + */ + @Override + public T next() throws NoSuchElementException { + if (!hasNext()) + throw new NoSuchElementException(); + return elements[index++]; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java new file mode 100644 index 0000000000..c0d8df5e43 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 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.utils; + +/** + * Utility methods for manipulating bit masks + */ +public class BitMask { + /** + * Returns true if all of the bits indicated by the mask are set. + */ + public static boolean isSet(int flags, int mask) { + return (flags & mask) == mask; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java new file mode 100644 index 0000000000..d634434084 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java @@ -0,0 +1,207 @@ +/******************************************************************************* + * 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.utils; + +import org.eclipse.core.runtime.Assert; + +/** + * A cache that keeps a collection of at most maximumCapacity+threshold entries. + * When the number of entries exceeds that limit, least recently used entries are removed + * so the current size is the same as the maximum capacity. + */ +public class Cache { + public class Entry implements KeyedHashSet.KeyedElement { + Object cached; + Object key; + Entry next; + Entry previous; + long timestamp; + + public Entry(Object key, Object cached, long timestamp) { + this.key = key; + this.cached = cached; + this.timestamp = timestamp; + } + + @Override + public boolean compare(KeyedHashSet.KeyedElement other) { + if (!(other instanceof Entry)) + return false; + Entry otherEntry = (Entry) other; + return key.equals(otherEntry.key); + } + + /* Removes this entry from the cache */ + public void discard() { + unchain(); + cached = null; + entries.remove(this); + } + + public Object getCached() { + return cached; + } + + @Override + public Object getKey() { + return key; + } + + @Override + public int getKeyHashCode() { + return key.hashCode(); + } + + public Entry getNext() { + return next; + } + + public Entry getPrevious() { + return previous; + } + + public long getTimestamp() { + return timestamp; + } + + public boolean isHead() { + return previous == null; + } + + public boolean isTail() { + return next == null; + } + + /* Inserts into the head of the list */ + void makeHead() { + Entry oldHead = head; + head = this; + next = oldHead; + previous = null; + if (oldHead == null) + tail = this; + else + oldHead.previous = this; + } + + public void setCached(Object cached) { + this.cached = cached; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public String toString() { + return key + " -> " + cached + " [" + timestamp + ']'; //$NON-NLS-1$ //$NON-NLS-2$ + } + + /* Removes from the linked list, but not from the entries collection */ + void unchain() { + // it may be in the tail + if (tail == this) + tail = previous; + else + next.previous = previous; + // it may be in the head + if (head == this) + head = next; + else + previous.next = next; + } + } + + KeyedHashSet entries; + Entry head; + private int maximumCapacity; + Entry tail; + private double threshold; + + public Cache(int maximumCapacity) { + this(Math.min(KeyedHashSet.MINIMUM_SIZE, maximumCapacity), maximumCapacity, 0.25); + } + + public Cache(int initialCapacity, int maximumCapacity, double threshold) { + Assert.isTrue(maximumCapacity >= initialCapacity, "maximum capacity < initial capacity"); //$NON-NLS-1$ + Assert.isTrue(threshold >= 0 && threshold <= 1, "threshold should be between 0 and 1"); //$NON-NLS-1$ + Assert.isTrue(initialCapacity > 0, "initial capacity must be greater than zero"); //$NON-NLS-1$ + entries = new KeyedHashSet(initialCapacity); + this.maximumCapacity = maximumCapacity; + this.threshold = threshold; + } + + public void addEntry(Object key, Object toCache) { + addEntry(key, toCache, 0); + } + + public Entry addEntry(Object key, Object toCache, long timestamp) { + Entry newHead = (Entry) entries.getByKey(key); + if (newHead == null) + entries.add(newHead = new Entry(key, toCache, timestamp)); + newHead.cached = toCache; + newHead.timestamp = timestamp; + newHead.makeHead(); + int extraEntries = entries.size() - maximumCapacity; + if (extraEntries > maximumCapacity * threshold) + // we have reached our limit - ensure we are under the maximum capacity + // by discarding older entries + packEntries(extraEntries); + return newHead; + } + + public Entry getEntry(Object key) { + return getEntry(key, true); + } + + public Entry getEntry(Object key, boolean update) { + Entry existing = (Entry) entries.getByKey(key); + if (existing == null) + return null; + if (!update) + return existing; + existing.unchain(); + existing.makeHead(); + return existing; + } + + public Entry getHead() { + return head; + } + + public Entry getTail() { + return tail; + } + + private void packEntries(int extraEntries) { + // should remove in an ad-hoc way to get better performance + Entry current = tail; + for (; current != null && extraEntries > 0; extraEntries--) { + current.discard(); + current = current.previous; + } + } + + public long size() { + return entries.size(); + } + + public void discardAll() { + entries.clear(); + head = tail = null; + } + + public void dispose() { + discardAll(); + entries = null; + head = tail = null; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java new file mode 100644 index 0000000000..78b86a8c94 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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.utils; + +import java.io.UnsupportedEncodingException; + +public class Convert { + + /** + * Converts the string argument to a byte array. + */ + public static String fromUTF8(byte[] b) { + String result; + try { + result = new String(b, "UTF8"); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + result = new String(b); + } + return result; + } + + /** + * Converts the string argument to a byte array. + */ + public static byte[] toUTF8(String s) { + byte[] result; + try { + result = s.getBytes("UTF8"); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + result = s.getBytes(); + } + return result; + } + + /** + * Performs conversion of a long value to a byte array representation. + * + * @see #bytesToLong(byte[]) + */ + public static byte[] longToBytes(long value) { + + // A long value is 8 bytes in length. + byte[] bytes = new byte[8]; + + // Convert and copy value to byte array: + // -- Cast long to a byte to retrieve least significant byte; + // -- Left shift long value by 8 bits to isolate next byte to be converted; + // -- Repeat until all 8 bytes are converted (long = 64 bits). + // Note: In the byte array, the least significant byte of the long is held in + // the highest indexed array bucket. + + for (int i = 0; i < bytes.length; i++) { + bytes[(bytes.length - 1) - i] = (byte) value; + value >>>= 8; + } + + return bytes; + } + + /** + * Performs conversion of a byte array to a long representation. + * + * @see #longToBytes(long) + */ + public static long bytesToLong(byte[] value) { + + long longValue = 0L; + + // See method convertLongToBytes(long) for algorithm details. + for (int i = 0; i < value.length; i++) { + // Left shift has no effect thru first iteration of loop. + longValue <<= 8; + longValue ^= value[i] & 0xFF; + } + + return longValue; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java new file mode 100644 index 0000000000..70e5afd1e7 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java @@ -0,0 +1,449 @@ +/******************************************************************************* + * 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 + * Martin Oberhuber (Wind River) - [44107] Add symbolic links to ResourceAttributes API + * James Blackburn (Broadcom Corp.) - ongoing development + * Sergey Prigogin (Google) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import java.io.*; +import java.net.URI; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.resources.ResourceException; +import org.eclipse.core.internal.resources.Workspace; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.preferences.DefaultScope; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.osgi.service.environment.Constants; +import org.eclipse.osgi.util.NLS; +import org.osgi.service.prefs.BackingStoreException; +import org.osgi.service.prefs.Preferences; + +/** + * Static utility methods for manipulating Files and URIs. + */ +public class FileUtil { + static final boolean MACOSX = Constants.OS_MACOSX.equals(getOS()); + + /** + * Singleton buffer created to prevent buffer creations in the + * transferStreams method. Used as an optimization, based on the assumption + * that multiple writes won't happen in a given instance of FileStore. + */ + private static final byte[] buffer = new byte[8192]; + + /** + * Converts a ResourceAttributes object into an IFileInfo object. + * @param attributes The resource attributes + * @return The file info + */ + public static IFileInfo attributesToFileInfo(ResourceAttributes attributes) { + IFileInfo fileInfo = EFS.createFileInfo(); + fileInfo.setAttribute(EFS.ATTRIBUTE_READ_ONLY, attributes.isReadOnly()); + fileInfo.setAttribute(EFS.ATTRIBUTE_EXECUTABLE, attributes.isExecutable()); + fileInfo.setAttribute(EFS.ATTRIBUTE_ARCHIVE, attributes.isArchive()); + fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, attributes.isHidden()); + fileInfo.setAttribute(EFS.ATTRIBUTE_SYMLINK, attributes.isSymbolicLink()); + fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_READ, attributes.isSet(EFS.ATTRIBUTE_GROUP_READ)); + fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_WRITE, attributes.isSet(EFS.ATTRIBUTE_GROUP_WRITE)); + fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE, attributes.isSet(EFS.ATTRIBUTE_GROUP_EXECUTE)); + fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_READ, attributes.isSet(EFS.ATTRIBUTE_OTHER_READ)); + fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_WRITE, attributes.isSet(EFS.ATTRIBUTE_OTHER_WRITE)); + fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE, attributes.isSet(EFS.ATTRIBUTE_OTHER_EXECUTE)); + return fileInfo; + } + + /** + * Converts an IPath into its canonical form for the local file system. + */ + public static IPath canonicalPath(IPath path) { + if (path == null) + return null; + try { + final String pathString = path.toOSString(); + final String canonicalPath = new java.io.File(pathString).getCanonicalPath(); + //only create a new path if necessary + if (canonicalPath.equals(pathString)) + return path; + return new Path(canonicalPath); + } catch (IOException e) { + return path; + } + } + + /** + * For a path on a case-insensitive file system returns the path with the actual + * case as it exists in the file system. If only a prefix of the path exists on + * the file system, the case of remaining part of the returned path is the same + * as in the original path. For a case-sensitive file system returns the original + * path. + *

+ * This method is similar to java.nio.file.Path.toRealPath(LinkOption.NOFOLLOW_LINKS) + * in Java 1.7. + */ + public static IPath realPath(IPath path) { + if (path == null) + return null; + IFileSystem fileSystem = EFS.getLocalFileSystem(); + if (fileSystem.isCaseSensitive()) + return path; + IPath realPath = path.isAbsolute() ? Path.ROOT : Path.EMPTY; + String device = path.getDevice(); + if (device != null) { + realPath = realPath.setDevice(device.toUpperCase()); + } + IFileStore fileStore = null; + for (int i = 0; i < path.segmentCount(); i++) { + final String segment = path.segment(i); + if (i == 0 && path.isUNC()) { + realPath = realPath.append(segment.toUpperCase()); + realPath = realPath.makeUNC(true); + } else { + if (MACOSX) { + // IFileInfo.getName() may not return the real name of the file on Mac OS X. + // Obtain the real name of the file from a listing of its parent directory. + String[] names = realPath.toFile().list(new FilenameFilter() { + @Override + public boolean accept(File dir, String n) { + return n.equalsIgnoreCase(segment); + } + }); + String realName; + if (names == null || names.length == 0) { + // The remainder of the path doesn't exist on the file system - copy from + // the original path. + realPath = realPath.append(path.removeFirstSegments(realPath.segmentCount())); + break; + } else if (names.length == 1) { + realName = names[0]; + } else { + // More than one file matches the file name. Maybe the file system was + // misreported to be case insensitive. Preserve the original name. + realName = segment; + } + realPath = realPath.append(realName); + } else { + if (fileStore == null) + fileStore = fileSystem.getStore(realPath); + fileStore = fileStore.getChild(segment); + IFileInfo info = fileStore.fetchInfo(); + if (!info.exists()) { + // The remainder of the path doesn't exist on the file system - copy from + // the original path. + realPath = realPath.append(path.removeFirstSegments(realPath.segmentCount())); + break; + } + realPath = realPath.append(info.getName()); + } + } + } + if (path.hasTrailingSeparator()) { + realPath = realPath.addTrailingSeparator(); + } + // Return the original path if it's the same as the real one. + return realPath.equals(path) ? path : realPath; + } + + /** + * Returns the current OS. Equivalent to Platform.getOS(), but tolerant of the platform runtime + * not being present. + */ + private static String getOS() { + return System.getProperty("osgi.os", ""); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Converts a URI into its canonical form. + */ + public static URI canonicalURI(URI uri) { + if (uri == null) + return null; + if (EFS.SCHEME_FILE.equals(uri.getScheme())) { + //only create a new URI if it is different + final IPath inputPath = URIUtil.toPath(uri); + final IPath canonicalPath = canonicalPath(inputPath); + if (inputPath == canonicalPath) + return uri; + return URIUtil.toURI(canonicalPath); + } + return uri; + } + + /** + * Converts a URI by replacing the file system path in the URI with the path + * with the actual case as it exists in the file system. + * + * @see #realPath(IPath) + */ + public static URI realURI(URI uri) { + if (uri == null) + return null; + if (EFS.SCHEME_FILE.equals(uri.getScheme())) { + // Only create a new URI if it is different. + final IPath inputPath = URIUtil.toPath(uri); + final IPath realPath = realPath(inputPath); + if (inputPath == realPath) + return uri; + return URIUtil.toURI(realPath); + } + return uri; + } + + /** + * Returns true if the given file system locations overlap. If "bothDirections" is true, + * this means they are the same, or one is a proper prefix of the other. If "bothDirections" + * is false, this method only returns true if the locations are the same, or the first location + * is a prefix of the second. Returns false if the locations do not overlap + * Does the right thing with respect to case insensitive platforms. + */ + private static boolean computeOverlap(IPath location1, IPath location2, boolean bothDirections) { + IPath one = location1; + IPath two = location2; + // If we are on a case-insensitive file system then convert to all lower case. + if (!Workspace.caseSensitive) { + one = new Path(location1.toOSString().toLowerCase()); + two = new Path(location2.toOSString().toLowerCase()); + } + return one.isPrefixOf(two) || (bothDirections && two.isPrefixOf(one)); + } + + /** + * Returns true if the given file system locations overlap. If "bothDirections" is true, + * this means they are the same, or one is a proper prefix of the other. If "bothDirections" + * is false, this method only returns true if the locations are the same, or the first location + * is a prefix of the second. Returns false if the locations do not overlap + */ + private static boolean computeOverlap(URI location1, URI location2, boolean bothDirections) { + if (location1.equals(location2)) + return true; + String scheme1 = location1.getScheme(); + String scheme2 = location2.getScheme(); + if (scheme1 == null ? scheme2 != null : !scheme1.equals(scheme2)) + return false; + if (EFS.SCHEME_FILE.equals(scheme1) && EFS.SCHEME_FILE.equals(scheme2)) + return computeOverlap(URIUtil.toPath(location1), URIUtil.toPath(location2), bothDirections); + IFileSystem system = null; + try { + system = EFS.getFileSystem(scheme1); + } catch (CoreException e) { + //handled below + } + if (system == null) { + //we are stuck with string comparison + String string1 = location1.toString(); + String string2 = location2.toString(); + return string1.startsWith(string2) || (bothDirections && string2.startsWith(string1)); + } + IFileStore store1 = system.getStore(location1); + IFileStore store2 = system.getStore(location2); + return store1.equals(store2) || store1.isParentOf(store2) || (bothDirections && store2.isParentOf(store1)); + } + + /** + * Converts an IFileInfo object into a ResourceAttributes object. + * @param fileInfo The file info + * @return The resource attributes + */ + public static ResourceAttributes fileInfoToAttributes(IFileInfo fileInfo) { + ResourceAttributes attributes = new ResourceAttributes(); + attributes.setReadOnly(fileInfo.getAttribute(EFS.ATTRIBUTE_READ_ONLY)); + attributes.setArchive(fileInfo.getAttribute(EFS.ATTRIBUTE_ARCHIVE)); + attributes.setExecutable(fileInfo.getAttribute(EFS.ATTRIBUTE_EXECUTABLE)); + attributes.setHidden(fileInfo.getAttribute(EFS.ATTRIBUTE_HIDDEN)); + attributes.setSymbolicLink(fileInfo.getAttribute(EFS.ATTRIBUTE_SYMLINK)); + attributes.set(EFS.ATTRIBUTE_GROUP_READ, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_READ)); + attributes.set(EFS.ATTRIBUTE_GROUP_WRITE, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_WRITE)); + attributes.set(EFS.ATTRIBUTE_GROUP_EXECUTE, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE)); + attributes.set(EFS.ATTRIBUTE_OTHER_READ, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_READ)); + attributes.set(EFS.ATTRIBUTE_OTHER_WRITE, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_WRITE)); + attributes.set(EFS.ATTRIBUTE_OTHER_EXECUTE, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE)); + return attributes; + } + + private static String getLineSeparatorFromPreferences(Preferences node) { + try { + // be careful looking up for our node so not to create any nodes as side effect + if (node.nodeExists(Platform.PI_RUNTIME)) + return node.node(Platform.PI_RUNTIME).get(Platform.PREF_LINE_SEPARATOR, null); + } catch (BackingStoreException e) { + // ignore + } + return null; + } + + /** + * Returns line separator appropriate for the given file. The returned value + * will be the first available value from the list below: + *

    + *
  1. Line separator currently used in that file. + *
  2. Line separator defined in project preferences. + *
  3. Line separator defined in instance preferences. + *
  4. Line separator defined in default preferences. + *
  5. Operating system default line separator. + *
+ * @param file the file for which line separator should be returned + * @return line separator for the given file + */ + public static String getLineSeparator(IFile file) { + if (file.exists()) { + InputStream input = null; + try { + input = file.getContents(); + int c = input.read(); + while (c != -1 && c != '\r' && c != '\n') + c = input.read(); + if (c == '\n') + return "\n"; //$NON-NLS-1$ + if (c == '\r') { + if (input.read() == '\n') + return "\r\n"; //$NON-NLS-1$ + return "\r"; //$NON-NLS-1$ + } + } catch (CoreException e) { + // ignore + } catch (IOException e) { + // ignore + } finally { + safeClose(input); + } + } + Preferences rootNode = Platform.getPreferencesService().getRootNode(); + String value = null; + // if the file does not exist or has no content yet, try with project preferences + value = getLineSeparatorFromPreferences(rootNode.node(ProjectScope.SCOPE).node(file.getProject().getName())); + if (value != null) + return value; + // try with instance preferences + value = getLineSeparatorFromPreferences(rootNode.node(InstanceScope.SCOPE)); + if (value != null) + return value; + // try with default preferences + value = getLineSeparatorFromPreferences(rootNode.node(DefaultScope.SCOPE)); + if (value != null) + return value; + // if there is no preference set, fall back to OS default value + return System.getProperty("line.separator"); //$NON-NLS-1$ + } + + /** + * Returns true if the given file system locations overlap, and false otherwise. + * Overlap means the locations are the same, or one is a proper prefix of the other. + */ + public static boolean isOverlapping(URI location1, URI location2) { + return computeOverlap(location1, location2, true); + } + + /** + * Returns true if location1 is the same as, or a proper prefix of, location2. + * Returns false otherwise. + */ + public static boolean isPrefixOf(IPath location1, IPath location2) { + return computeOverlap(location1, location2, false); + } + + /** + * Returns true if location1 is the same as, or a proper prefix of, location2. + * Returns false otherwise. + */ + public static boolean isPrefixOf(URI location1, URI location2) { + return computeOverlap(location1, location2, false); + } + + /** + * Closes a stream and ignores any resulting exception. This is useful + * when doing stream cleanup in a finally block where secondary exceptions + * are not worth logging. + * + *

+ * WARNING: + * If the API contract requires notifying clients of I/O problems, then you must + * explicitly close() output streams outside of safeClose(). + * Some OutputStreams will defer an IOException from write() to close(). So + * while the writes may 'succeed', ignoring the IOExcpetion will result in silent + * data loss. + *

+ *

+ * This method should only be used as a fail-safe to ensure resources are not + * leaked. + *

+ * See also: https://bugs.eclipse.org/bugs/show_bug.cgi?id=332543 + */ + public static void safeClose(Closeable stream) { + try { + if (stream != null) + stream.close(); + } catch (IOException e) { + //ignore + } + } + + /** + * Converts a URI to an IPath. Returns null if the URI cannot be represented + * as an IPath. + *

+ * Note this method differs from URIUtil in its handling of relative URIs + * as being relative to path variables. + */ + public static IPath toPath(URI uri) { + if (uri == null) + return null; + final String scheme = uri.getScheme(); + // null scheme represents path variable + if (scheme == null || EFS.SCHEME_FILE.equals(scheme)) + return new Path(uri.getSchemeSpecificPart()); + return null; + } + + public static final void transferStreams(InputStream source, OutputStream destination, String path, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + /* + * Note: although synchronizing on the buffer is thread-safe, + * it may result in slower performance in the future if we want + * to allow concurrent writes. + */ + synchronized (buffer) { + while (true) { + int bytesRead = -1; + try { + bytesRead = source.read(buffer); + } catch (IOException e) { + String msg = NLS.bind(Messages.localstore_failedReadDuringWrite, path); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, new Path(path), msg, e); + } + try { + if (bytesRead == -1) { + // Bug 332543 - ensure we don't ignore failures on close() + destination.close(); + break; + } + destination.write(buffer, 0, bytesRead); + } catch (IOException e) { + String msg = NLS.bind(Messages.localstore_couldNotWrite, path); + throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, new Path(path), msg, e); + } + monitor.worked(1); + } + } + } finally { + safeClose(source); + safeClose(destination); + } + } + + /** + * Not intended for instantiation. + */ + private FileUtil() { + super(); + } +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.java new file mode 100644 index 0000000000..3cdef966a4 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.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.utils; + +/** + * A string pool participant is used for sharing strings between several + * unrelated parties. Typically a single StringPool instance + * will be created, and a group of participants will be asked to store their + * strings in the pool. This allows participants to share equal strings + * without creating explicit dependencies between each other. + *

+ * Clients may implement this interface. + *

+ * + * @see StringPool + * @since 3.1 + */ +public interface IStringPoolParticipant { + /** + * Instructs this participant to share its strings in the provided + * pool. + */ + public void shareStrings(StringPool pool); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java new file mode 100644 index 0000000000..8465dc48d5 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java @@ -0,0 +1,243 @@ +/******************************************************************************* + * 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.utils; + + +/** Adapted from a homonym class in org.eclipse.osgi. A hash table of + * KeyedElements. + */ + +public class KeyedHashSet { + public interface KeyedElement { + + public boolean compare(KeyedElement other); + + public Object getKey(); + + public int getKeyHashCode(); + } + + protected static final int MINIMUM_SIZE = 7; + private int capacity; + protected int elementCount = 0; + protected KeyedElement[] elements; + protected boolean replace; + + public KeyedHashSet(int capacity) { + this(capacity, true); + } + + public KeyedHashSet(int capacity, boolean replace) { + elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)]; + this.replace = replace; + this.capacity = capacity; + } + + /** + * Adds an element to this set. If an element with the same key already exists, + * replaces it depending on the replace flag. + * @return true if the element was added/stored, false otherwise + */ + public boolean add(KeyedElement element) { + int hash = hash(element); + + // 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 true; + } + if (elements[i].compare(element)) { + if (replace) + elements[i] = element; + return replace; + } + } + + // 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 true; + } + if (elements[i].compare(element)) { + if (replace) + elements[i] = element; + return replace; + } + } + + // if we didn't find a free slot, then try again with the expanded set + expand(); + return add(element); + } + + public void clear() { + elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)]; + elementCount = 0; + } + + /** + * The array isn't large enough so double its size and rehash + * all its current values. + */ + protected void expand() { + KeyedElement[] oldElements = elements; + elements = new KeyedElement[elements.length * 2]; + + int maxArrayIndex = elements.length - 1; + for (int i = 0; i < oldElements.length; i++) { + KeyedElement element = oldElements[i]; + if (element != null) { + int hash = hash(element); + while (elements[hash] != null) { + hash++; + if (hash > maxArrayIndex) + hash = 0; + } + elements[hash] = element; + } + } + } + + /** + * Returns the set element with the given id, or null + * if not found. + */ + public KeyedElement getByKey(Object key) { + if (elementCount == 0) + return null; + int hash = keyHash(key); + + // search the last half of the array + for (int i = hash; i < elements.length; i++) { + KeyedElement element = elements[i]; + if (element == null) + return null; + if (element.getKey().equals(key)) + return element; + } + + // search the beginning of the array + for (int i = 0; i < hash - 1; i++) { + KeyedElement element = elements[i]; + if (element == null) + return null; + if (element.getKey().equals(key)) + return element; + } + + // nothing found so return null + return null; + } + + private int hash(KeyedElement key) { + return Math.abs(key.getKeyHashCode()) % elements.length; + } + + private int keyHash(Object key) { + return Math.abs(key.hashCode()) % elements.length; + } + + /** + * 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; + KeyedElement element = elements[index]; + while (element != null) { + int hashIndex = hash(element); + 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 boolean remove(KeyedElement toRemove) { + if (elementCount == 0) + return false; + + int hash = hash(toRemove); + + for (int i = hash; i < elements.length; i++) { + KeyedElement element = elements[i]; + if (element == null) + return false; + if (element.compare(toRemove)) { + rehashTo(i); + elementCount--; + return true; + } + } + + for (int i = 0; i < hash - 1; i++) { + KeyedElement element = elements[i]; + if (element == null) + return false; + if (element.compare(toRemove)) { + rehashTo(i); + elementCount--; + return true; + } + } + return false; + } + + private boolean shouldGrow() { + return elementCount > elements.length * 0.75; + } + + public int size() { + return elementCount; + } + + @Override + public String toString() { + StringBuffer result = new StringBuffer(100); + result.append('{'); + boolean first = true; + for (int i = 0; i < elements.length; i++) { + if (elements[i] != null) { + if (first) + first = false; + else + result.append(", "); //$NON-NLS-1$ + result.append(elements[i]); + } + } + result.append('}'); + return result.toString(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java new file mode 100644 index 0000000000..34374bede1 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java @@ -0,0 +1,328 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + * Francis Lynch (Wind River) - [301563] Save and load tree snapshots + * Martin Oberhuber (Wind River) - [306575] Save snapshot location with project + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.core.internal.utils.messages"; //$NON-NLS-1$ + + // dtree + public static String dtree_immutable; + public static String dtree_malformedTree; + public static String dtree_missingChild; + public static String dtree_notFound; + public static String dtree_notImmutable; + public static String dtree_reverse; + public static String dtree_subclassImplement; + public static String dtree_switchError; + + // events + public static String events_builderError; + public static String events_building_0; + public static String events_building_1; + public static String events_errors; + public static String events_instantiate_1; + public static String events_invoking_1; + public static String events_invoking_2; + public static String events_skippingBuilder; + public static String events_unknown; + + public static String history_copyToNull; + public static String history_copyToSelf; + public static String history_errorContentDescription; + public static String history_notValid; + public static String history_problemsCleaning; + + public static String links_creating; + public static String links_errorLinkReconcile; + public static String links_invalidLocation; + public static String links_localDoesNotExist; + public static String links_locationOverlapsLink; + public static String links_locationOverlapsProject; + public static String links_natureVeto; + public static String links_noPath; + public static String links_overlappingResource; + public static String links_parentNotAccessible; + public static String links_notFileFolder; + public static String links_updatingDuplicate; + public static String links_vetoNature; + public static String links_workspaceVeto; + public static String links_wrongLocalType; + public static String links_resourceIsNotALink; + public static String links_setLocation; + + public static String group_invalidParent; + public static String filters_missingFilterType; + + // local store + public static String localstore_copying; + public static String localstore_copyProblem; + public static String localstore_couldnotDelete; + public static String localstore_couldNotMove; + public static String localstore_couldNotRead; + public static String localstore_couldNotWrite; + public static String localstore_couldNotWriteReadOnly; + public static String localstore_deleteProblem; + public static String localstore_deleting; + public static String localstore_failedReadDuringWrite; + public static String localstore_fileExists; + public static String localstore_fileNotFound; + public static String localstore_locationUndefined; + public static String localstore_refreshing; + public static String localstore_refreshingRoot; + public static String localstore_resourceExists; + public static String localstore_resourceDoesNotExist; + public static String localstore_resourceIsOutOfSync; + + // resource mappings and models + public static String mapping_invalidDef; + public static String mapping_wrongType; + public static String mapping_noIdentifier; + public static String mapping_validate; + public static String mapping_multiProblems; + + // internal.resources + public static String natures_duplicateNature; + public static String natures_hasCycle; + public static String natures_invalidDefinition; + public static String natures_invalidRemoval; + public static String natures_invalidSet; + public static String natures_missingIdentifier; + public static String natures_missingNature; + public static String natures_missingPrerequisite; + public static String natures_multipleSetMembers; + + public static String pathvar_beginLetter; + public static String pathvar_invalidChar; + public static String pathvar_invalidValue; + public static String pathvar_length; + public static String pathvar_undefined; + public static String pathvar_whitespace; + + public static String preferences_deleteException; + public static String preferences_loadException; + public static String preferences_operationCanceled; + public static String preferences_removeNodeException; + public static String preferences_clearNodeException; + public static String preferences_saveProblems; + public static String preferences_syncException; + + public static String projRead_badArguments; + public static String projRead_badFilterName; + public static String projRead_badFilterID; + public static String projRead_badFilterType; + public static String projRead_badFilterType2; + public static String projRead_badID; + public static String projRead_badLinkLocation; + public static String projRead_badLinkName; + public static String projRead_badLinkType; + public static String projRead_badLinkType2; + public static String projRead_badLocation; + public static String projRead_badSnapshotLocation; + public static String projRead_cannotReadSnapshot; + public static String projRead_emptyFilterName; + public static String projRead_emptyLinkName; + public static String projRead_emptyVariableName; + public static String projRead_failureReadingProjectDesc; + public static String projRead_notProjectDescription; + public static String projRead_whichKey; + public static String projRead_whichValue; + public static String projRead_missingProjectName; + + public static String properties_couldNotClose; + public static String properties_qualifierIsNull; + public static String properties_readProperties; + public static String properties_valueTooLong; + + // auto-refresh + public static String refresh_installError; + public static String refresh_jobName; + public static String refresh_pollJob; + public static String refresh_refreshErr; + public static String refresh_task; + + public static String resources_cannotModify; + public static String resources_changeInAdd; + public static String resources_charsetBroadcasting; + public static String resources_charsetUpdating; + public static String resources_closing_0; + public static String resources_closing_1; + public static String resources_copyDestNotSub; + public static String resources_copying; + public static String resources_copying_0; + public static String resources_copyNotMet; + public static String resources_copyProblem; + public static String resources_couldnotDelete; + public static String resources_create; + public static String resources_creating; + public static String resources_deleteMeta; + public static String resources_deleteProblem; + public static String resources_deleting; + public static String resources_deleting_0; + public static String resources_destNotNull; + public static String resources_errorContentDescription; + public static String resources_errorDeleting; + public static String resources_errorMarkersDelete; + public static String resources_errorMarkersMove; + public static String resources_wrongMarkerAttributeValueType; + public static String resources_errorMembers; + public static String resources_errorMoving; + public static String resources_errorMultiRefresh; + public static String resources_errorNature; + public static String resources_errorPropertiesMove; + public static String resources_errorReadProject; + public static String resources_errorRefresh; + public static String resources_errorValidator; + public static String resources_errorVisiting; + public static String resources_existsDifferentCase; + public static String resources_existsLocalDifferentCase; + public static String resources_exMasterTable; + public static String resources_exReadProjectLocation; + public static String resources_exSafeRead; + public static String resources_exSafeSave; + public static String resources_exSaveMaster; + public static String resources_exSaveProjectLocation; + public static String resources_fileExists; + public static String resources_fileToProj; + public static String resources_flushingContentDescriptionCache; + public static String resources_folderOverFile; + public static String resources_format; + public static String resources_initHook; + public static String resources_initTeamHook; + public static String resources_initValidator; + public static String resources_invalidCharInName; + public static String resources_invalidCharInPath; + public static String resources_invalidName; + public static String resources_invalidPath; + public static String resources_invalidProjDesc; + public static String resources_invalidResourceName; + public static String resources_invalidRoot; + public static String resources_markerNotFound; + public static String resources_missingProjectMeta; + public static String resources_missingProjectMetaRepaired; + public static String resources_moveDestNotSub; + public static String resources_moveMeta; + public static String resources_moveNotMet; + public static String resources_moveNotProject; + public static String resources_moveProblem; + public static String resources_moveRoot; + public static String resources_moving; + public static String resources_moving_0; + public static String resources_mustBeAbsolute; + public static String resources_mustBeLocal; + public static String resources_mustBeOpen; + public static String resources_mustExist; + public static String resources_mustNotExist; + public static String resources_nameEmpty; + public static String resources_nameNull; + public static String resources_natureClass; + public static String resources_natureDeconfig; + public static String resources_natureExtension; + public static String resources_natureFormat; + public static String resources_natureImplement; + public static String resources_notChild; + public static String resources_oneHook; + public static String resources_oneTeamHook; + public static String resources_oneValidator; + public static String resources_opening_1; + public static String resources_overlapWorkspace; + public static String resources_overlapProject; + public static String resources_pathNull; + public static String resources_projectDesc; + public static String resources_projectDescSync; + public static String resources_projectMustNotBeOpen; + public static String resources_projectPath; + public static String resources_pruningHistory; + public static String resources_reading; + public static String resources_readingEncoding; + public static String resources_readingSnap; + public static String resources_readMarkers; + public static String resources_readMeta; + public static String resources_readMetaWrongVersion; + public static String resources_readOnly; + public static String resources_readOnly2; + public static String resources_readProjectMeta; + public static String resources_readProjectTree; + public static String resources_readSync; + public static String resources_readWorkspaceMeta; + public static String resources_readWorkspaceMetaValue; + public static String resources_readWorkspaceSnap; + public static String resources_readWorkspaceTree; + public static String resources_refreshing; + public static String resources_refreshingRoot; + public static String resources_resetMarkers; + public static String resources_resetSync; + public static String resources_resourcePath; + public static String resources_saveOp; + public static String resources_saveProblem; + public static String resources_saveWarnings; + public static String resources_saving_0; + public static String resources_savingEncoding; + public static String resources_setDesc; + public static String resources_setLocal; + public static String resources_settingCharset; + public static String resources_settingContents; + public static String resources_settingDefaultCharsetContainer; + public static String resources_settingDerivedFlag; + public static String resources_shutdown; + public static String resources_shutdownProblems; + public static String resources_snapInit; + public static String resources_snapRead; + public static String resources_snapRequest; + public static String resources_snapshot; + public static String resources_startupProblems; + public static String resources_touch; + public static String resources_updating; + public static String resources_updatingEncoding; + public static String resources_workspaceClosed; + public static String resources_workspaceOpen; + public static String resources_writeMeta; + public static String resources_writeWorkspaceMeta; + public static String resources_errorResourceIsFiltered; + + public static String synchronizer_partnerNotRegistered; + + // URL + public static String url_badVariant; + public static String url_couldNotResolve_projectDoesNotExist; + public static String url_couldNotResolve_URLProtocolHandlerCanNotResolveURL; + public static String url_couldNotResolve_resourceLocationCanNotBeDetermined; + + // utils + public static String utils_clone; + public static String utils_stringJobName; + // watson + public static String watson_elementNotFound; + public static String watson_illegalSubtree; + public static String watson_immutable; + public static String watson_noModify; + public static String watson_nullArg; + public static String watson_unknown; + + // auto-refresh win32 native + public static String WM_beginTask; + public static String WM_errCloseHandle; + public static String WM_errCreateHandle; + public static String WM_errFindChange; + public static String WM_errors; + public static String WM_jobName; + public static String WM_nativeErr; + + static { + // initialize resource bundles + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java new file mode 100644 index 0000000000..e4cdb772e5 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java @@ -0,0 +1,329 @@ +/******************************************************************************* + * 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.utils; + +import java.util.*; + +/** + * A specialized map implementation that is optimized for a + * small set of object keys. + * + * Implemented as a single array that alternates keys and values. + */ +@SuppressWarnings("unchecked") +public class ObjectMap implements Map, IStringPoolParticipant { + + // 8 attribute keys, 8 attribute values + protected static final int DEFAULT_SIZE = 16; + protected static final int GROW_SIZE = 10; + protected int count = 0; + protected Object[] elements = null; + + /** + * Creates a new object map of default size + */ + public ObjectMap() { + this(DEFAULT_SIZE); + } + + /** + * Creates a new object map. + * @param initialCapacity The initial number of elements that will fit in the map. + */ + public ObjectMap(int initialCapacity) { + if (initialCapacity > 0) + elements = new Object[Math.max(initialCapacity * 2, 0)]; + } + + /** + * Creates a new object map of the same size as the given map and + * populate it with the key/attribute pairs found in the map. + * @param map The entries in the given map will be added to the new map. + */ + public ObjectMap(Map map) { + this(map.size()); + putAll(map); + } + + /** + * @see Map#clear() + */ + @Override + public void clear() { + elements = null; + count = 0; + } + + /** + * @see java.lang.Object#clone() + */ + @Override + public Object clone() { + return new ObjectMap(this); + } + + /** + * @see Map#containsKey(java.lang.Object) + */ + @Override + public boolean containsKey(Object key) { + if (elements == null || count == 0) + return false; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return true; + return false; + } + + /** + * @see Map#containsValue(java.lang.Object) + */ + @Override + public boolean containsValue(Object value) { + if (elements == null || 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; + } + + /** + * @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 count == 0 ? Collections.EMPTY_SET : toHashMap().entrySet(); + } + + /** + * See Object#equals + */ + @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; + + //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; + } + + /** + * @see Map#get(java.lang.Object) + */ + @Override + public V get(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return (V) elements[i + 1]; + return null; + } + + /** + * The capacity of the map has been exceeded, grow the array by + * GROW_SIZE to accommodate more entries. + */ + protected void grow() { + Object[] expanded = new Object[elements.length + GROW_SIZE]; + System.arraycopy(elements, 0, expanded, 0, elements.length); + elements = expanded; + } + + /** + * See Object#hashCode + */ + @Override + public int hashCode() { + int hash = 0; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + hash += elements[i].hashCode(); + } + } + return hash; + } + + /** + * @see Map#isEmpty() + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * @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()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.add((K) elements[i]); + } + } + return result; + } + + /** + * @see Map#put(java.lang.Object, java.lang.Object) + */ + @Override + public V put(K key, V value) { + if (key == null) + throw new NullPointerException(); + if (value == null) + return remove(key); + + // handle the case where we don't have any attributes yet + if (elements == null) + elements = new Object[DEFAULT_SIZE]; + if (count == 0) { + elements[0] = key; + elements[1] = value; + count++; + return null; + } + + int emptyIndex = -1; + // replace existing value if it exists + for (int i = 0; i < elements.length; i += 2) { + if (elements[i] != null) { + if (elements[i].equals(key)) { + Object oldValue = elements[i + 1]; + elements[i + 1] = value; + return (V) oldValue; + } + } else if (emptyIndex == -1) { + // keep track of the first empty index + emptyIndex = i; + } + } + // this will put the emptyIndex greater than the size but + // that's ok because we will grow first. + if (emptyIndex == -1) + emptyIndex = count * 2; + + // otherwise add it to the list of elements. + // grow if necessary + if (elements.length <= (count * 2)) + grow(); + elements[emptyIndex] = key; + elements[emptyIndex + 1] = value; + count++; + return null; + } + + /** + * @see Map#putAll(java.util.Map) + */ + @Override + public void putAll(Map map) { + for (Map.Entry e : map.entrySet()) + put(e.getKey(), e.getValue()); + } + + /** + * @see Map#remove(java.lang.Object) + */ + @Override + public V remove(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null && elements[i].equals(key)) { + elements[i] = null; + Object result = elements[i + 1]; + elements[i + 1] = null; + count--; + return (V) result; + } + } + return null; + } + + /** + * @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; + 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); + } + } + + /** + * Creates a new hash map with the same contents as this map. + */ + private HashMap toHashMap() { + HashMap result = new HashMap(size()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.put((K) elements[i], (V) elements[i + 1]); + } + } + return result; + } + + /** + * @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()); + 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/utils/Policy.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java new file mode 100644 index 0000000000..1dcf3d6a80 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java @@ -0,0 +1,190 @@ +/******************************************************************************* + * 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.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.service.debug.DebugOptions; +import org.eclipse.osgi.service.debug.DebugOptionsListener; +import org.osgi.framework.Bundle; + +public class Policy { + public static final DebugOptionsListener RESOURCES_DEBUG_OPTIONS_LISTENER = new DebugOptionsListener() { + @Override + public void optionsChanged(DebugOptions options) { + DEBUG = options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/debug", false); //$NON-NLS-1$ + + DEBUG_AUTO_REFRESH = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/refresh", false); //$NON-NLS-1$ + + DEBUG_BUILD_DELTA = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/delta", false); //$NON-NLS-1$ + DEBUG_BUILD_FAILURE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/failure", false); //$NON-NLS-1$ + DEBUG_BUILD_INTERRUPT = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/interrupt", false); //$NON-NLS-1$ + DEBUG_BUILD_INVOKING = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/invoking", false); //$NON-NLS-1$ + DEBUG_BUILD_NEEDED = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/needbuild", false); //$NON-NLS-1$ + DEBUG_BUILD_NEEDED_STACK = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/needbuildstack", false); //$NON-NLS-1$ + DEBUG_BUILD_STACK = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/stacktrace", false); //$NON-NLS-1$ + + DEBUG_CONTENT_TYPE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/contenttype", false); //$NON-NLS-1$ + DEBUG_CONTENT_TYPE_CACHE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/contenttype/cache", false); //$NON-NLS-1$ + DEBUG_HISTORY = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/history", false); //$NON-NLS-1$ + DEBUG_NATURES = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/natures", false); //$NON-NLS-1$ + DEBUG_NOTIFICATIONS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/notifications", false); //$NON-NLS-1$ + DEBUG_PREFERENCES = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/preferences", false); //$NON-NLS-1$ + + DEBUG_RESTORE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore", false); //$NON-NLS-1$ + DEBUG_RESTORE_MARKERS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/markers", false); //$NON-NLS-1$ + DEBUG_RESTORE_MASTERTABLE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/mastertable", false); //$NON-NLS-1$ + DEBUG_RESTORE_METAINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/metainfo", false); //$NON-NLS-1$ + DEBUG_RESTORE_SNAPSHOTS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/snapshots", false); //$NON-NLS-1$ + DEBUG_RESTORE_SYNCINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/syncinfo", false); //$NON-NLS-1$ + DEBUG_RESTORE_TREE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/tree", false); //$NON-NLS-1$ + + DEBUG_SAVE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save", false); //$NON-NLS-1$ + DEBUG_SAVE_MARKERS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/markers", false); //$NON-NLS-1$ + DEBUG_SAVE_MASTERTABLE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/mastertable", false); //$NON-NLS-1$ + DEBUG_SAVE_METAINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/metainfo", false); //$NON-NLS-1$ + DEBUG_SAVE_SYNCINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/syncinfo", false); //$NON-NLS-1$ + DEBUG_SAVE_TREE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/tree", false); //$NON-NLS-1$ + + DEBUG_STRINGS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/strings", false); //$NON-NLS-1$ + } + }; + + public static final boolean buildOnCancel = false; + //general debug flag for the plugin + public static boolean DEBUG = false; + + public static boolean DEBUG_AUTO_REFRESH = false; + + //debug constants + public static boolean DEBUG_BUILD_DELTA = false; + public static boolean DEBUG_BUILD_FAILURE = false; + public static boolean DEBUG_BUILD_INTERRUPT = false; + public static boolean DEBUG_BUILD_INVOKING = false; + public static boolean DEBUG_BUILD_NEEDED = false; + public static boolean DEBUG_BUILD_NEEDED_STACK = false; + public static boolean DEBUG_BUILD_STACK = false; + + public static boolean DEBUG_CONTENT_TYPE = false; + public static boolean DEBUG_CONTENT_TYPE_CACHE = false; + public static boolean DEBUG_HISTORY = false; + public static boolean DEBUG_NATURES = false; + public static boolean DEBUG_NOTIFICATIONS = false; + public static boolean DEBUG_PREFERENCES = false; + // Get timing information for restoring data + public static boolean DEBUG_RESTORE = false; + public static boolean DEBUG_RESTORE_MARKERS = false; + public static boolean DEBUG_RESTORE_MASTERTABLE = false; + + public static boolean DEBUG_RESTORE_METAINFO = false; + public static boolean DEBUG_RESTORE_SNAPSHOTS = false; + public static boolean DEBUG_RESTORE_SYNCINFO = false; + public static boolean DEBUG_RESTORE_TREE = false; + // Get timing information for save and snapshot data + public static boolean DEBUG_SAVE = false; + public static boolean DEBUG_SAVE_MARKERS = false; + public static boolean DEBUG_SAVE_MASTERTABLE = false; + + public static boolean DEBUG_SAVE_METAINFO = false; + public static boolean DEBUG_SAVE_SYNCINFO = false; + public static boolean DEBUG_SAVE_TREE = false; + public static boolean DEBUG_STRINGS = false; + public static int endOpWork = 1; + public static final long MAX_BUILD_DELAY = 1000; + + public static final long MIN_BUILD_DELAY = 100; + public static int opWork = 99; + public static final int totalWork = 100; + + public static void checkCanceled(IProgressMonitor monitor) { + if (monitor.isCanceled()) + throw new OperationCanceledException(); + } + + /** + * Print a debug message to the console. + * Prepend the message with the current date, the name of the current thread and the current job if present. + */ + public static void debug(String message) { + StringBuilder output = new StringBuilder(); + output.append(new Date(System.currentTimeMillis())); + output.append(" - ["); //$NON-NLS-1$ + output.append(Thread.currentThread().getName()); + output.append("] "); //$NON-NLS-1$ + Job currentJob = Job.getJobManager().currentJob(); + if (currentJob != null) { + output.append(currentJob.getClass().getName()); + output.append("("); //$NON-NLS-1$ + output.append(currentJob.getName()); + output.append("): "); //$NON-NLS-1$ + } + output.append(message); + System.out.println(output.toString()); + } + + /** + * Print a debug throwable to the console. + */ + public static void debug(Throwable t) { + StringWriter writer = new StringWriter(); + t.printStackTrace(new PrintWriter(writer)); + String str = writer.toString(); + if (str.endsWith("\n")) //$NON-NLS-1$ + str = str.substring(0, str.length() - 2); + debug(str); + } + + public static void log(int severity, String message, Throwable t) { + if (message == null) + message = ""; //$NON-NLS-1$ + log(new Status(severity, ResourcesPlugin.PI_RESOURCES, 1, message, t)); + } + + public static void log(IStatus status) { + final Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES); + if (bundle == null) + return; + Platform.getLog(bundle).log(status); + } + + /** + * Logs a throwable, assuming severity of error + * @param t + */ + public static void log(Throwable t) { + log(IStatus.ERROR, "Internal Error", t); //$NON-NLS-1$ + } + + public static IProgressMonitor monitorFor(IProgressMonitor monitor) { + return monitor == null ? new NullProgressMonitor() : monitor; + } + + public static IProgressMonitor subMonitorFor(IProgressMonitor monitor, int ticks) { + if (monitor == null) + return new NullProgressMonitor(); + if (monitor instanceof NullProgressMonitor) + return monitor; + return new SubProgressMonitor(monitor, ticks); + } + + public static IProgressMonitor subMonitorFor(IProgressMonitor monitor, int ticks, int style) { + if (monitor == null) + return new NullProgressMonitor(); + if (monitor instanceof NullProgressMonitor) + return monitor; + return new SubProgressMonitor(monitor, ticks, style); + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java new file mode 100644 index 0000000000..f8986cc1e4 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * 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.utils; + +import java.util.Collections; +import java.util.Iterator; + +/** + * A Queue of objects. + */ +@SuppressWarnings("unchecked") +public class Queue { + protected Object[] elements; + protected int head; + protected int tail; + protected boolean reuse; + + public Queue() { + this(20, false); + } + + /** + * The parameter reuse indicates what do you want to happen with + * the object reference when you remove it from the queue. If + * reuse is false the queue no longer holds a reference to the + * object when it is removed. If reuse is true you can use the + * method getNextAvailableObject to get an used object, set its + * new values and add it again to the queue. + */ + public Queue(int size, boolean reuse) { + elements = new Object[size]; + head = tail = 0; + this.reuse = reuse; + } + + public void add(T element) { + int newTail = increment(tail); + if (newTail == head) { + grow(); + newTail = tail + 1; + } + elements[tail] = element; + tail = newTail; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to decrement an index in the queue. + */ + public int decrement(int index) { + return (index == 0) ? (elements.length - 1) : index - 1; + } + + public T elementAt(int index) { + return (T)elements[index]; + } + + @SuppressWarnings("rawtypes") + public Iterator iterator() { + /**/ + if (isEmpty()) + return Collections.EMPTY_LIST.iterator(); + + /* if head < tail we can use the same array */ + if (head <= tail) + return new ArrayIterator(elements, head, tail - 1); + + /* otherwise we need to create a new array */ + Object[] newElements = new Object[size()]; + int end = (elements.length - head); + System.arraycopy(elements, head, newElements, 0, end); + System.arraycopy(elements, 0, newElements, end, tail); + return new ArrayIterator(newElements); + } + + /** + * Returns an object that has been removed from the queue, if any. + * The intention is to support reuse of objects that have already + * been processed and removed from the queue. Returns null if there + * are no available objects. + */ + public T getNextAvailableObject() { + int index = tail; + while (index != head) { + if (elements[index] != null) { + T result = (T)elements[index]; + elements[index] = null; + return result; + } + index = increment(index); + } + return null; + } + + protected void grow() { + int newSize = (int) (elements.length * 1.5); + Object[] newElements = new Object[newSize]; + if (tail >= head) + System.arraycopy(elements, head, newElements, head, size()); + else { + int newHead = newSize - (elements.length - head); + System.arraycopy(elements, 0, newElements, 0, tail + 1); + System.arraycopy(elements, head, newElements, newHead, (newSize - newHead)); + head = newHead; + } + elements = newElements; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to increment an index in the queue. + */ + public int increment(int index) { + return (index == (elements.length - 1)) ? 0 : index + 1; + } + + public int indexOf(T target) { + if (tail >= head) { + for (int i = head; i < tail; i++) + if (target.equals(elements[i])) + return i; + } else { + for (int i = head; i < elements.length; i++) + if (target.equals(elements[i])) + return i; + for (int i = 0; i < tail; i++) + if (target.equals(elements[i])) + return i; + } + return -1; + } + + public boolean isEmpty() { + return tail == head; + } + + public T peek() { + return (T)elements[head]; + } + + public T peekTail() { + return (T)elements[decrement(tail)]; + } + + public T remove() { + if (isEmpty()) + return null; + T result = peek(); + if (!reuse) + elements[head] = null; + head = increment(head); + return result; + } + + public T removeTail() { + T result = peekTail(); + tail = decrement(tail); + if (!reuse) + elements[tail] = null; + return result; + } + + public void reset() { + tail = head = 0; + } + + public int size() { + return tail > head ? (tail - head) : ((elements.length - head) + tail); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append('['); + int count = 0; + if (!isEmpty()) { + Iterator it = iterator(); + //only print a fixed number of elements to prevent debugger from choking + while (count < 100) { + sb.append(it.next()); + if (it.hasNext()) + sb.append(',').append(' '); + else + break; + } + } + if (count < size()) + sb.append('.').append('.').append('.'); + sb.append(']'); + return sb.toString(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java new file mode 100644 index 0000000000..b696d02dbe --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * 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 - Initial API and implementation + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import java.util.HashMap; + +/** + * A string pool is used for sharing strings in a way that eliminates duplicate + * equal strings. A string pool instance can be maintained over a long period + * of time, or used as a temporary structure during a string sharing pass over + * a data structure. + *

+ * This class is not intended to be subclassed by clients. + *

+ * + * @see IStringPoolParticipant + * @since 3.1 + */ +public final class StringPool { + private int savings; + private final HashMap map = new HashMap(); + + /** + * Creates a new string pool. + */ + public StringPool() { + super(); + } + + /** + * Adds a String to the pool. Returns a String + * that is equal to the argument but that is unique within this pool. + * @param string The string to add to the pool + * @return A string that is equal to the argument. + */ + public String add(String string) { + if (string == null) + return string; + Object result = map.get(string); + if (result != null) { + if (result != string) + savings += 44 + 2 * string.length(); + return (String) result; + } + map.put(string, string); + return string; + } + + /** + * Returns an estimate of the size in bytes that was saved by sharing strings in + * the pool. In particular, this returns the size of all strings that were added to the + * pool after an equal string had already been added. This value can be used + * to estimate the effectiveness of a string sharing operation, in order to + * determine if or when it should be performed again. + * + * In some cases this does not precisely represent the number of bytes that + * were saved. For example, say the pool already contains string S1. Now + * string S2, which is equal to S1 but not identical, is added to the pool five + * times. This method will return the size of string S2 multiplied by the + * number of times it was added, even though the actual savings in this case + * is only the size of a single copy of S2. + */ + public int getSavedStringCount() { + return savings; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java new file mode 100644 index 0000000000..1a2016d968 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * 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.utils; + +import java.util.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.*; +import org.osgi.framework.Bundle; + +/** + * Performs string sharing passes on all string pool participants registered + * with the platform. + */ +public class StringPoolJob extends Job { + private static final long INITIAL_DELAY = 10000;//ten seconds + private static final long RESCHEDULE_DELAY = 300000;//five minutes + private long lastDuration; + /** + * Stores all registered string pool participants, along with the scheduling + * rule required when running it. + */ + private Map participants = Collections.synchronizedMap(new HashMap(10)); + + private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$ + + public StringPoolJob() { + super(Messages.utils_stringJobName); + setSystem(true); + setPriority(DECORATE); + } + + /** + * Adds a string pool participant. The job periodically builds + * a string pool and asks all registered participants to share their strings in + * the pool. Once all participants have added their strings to the pool, the + * pool is discarded to avoid additional memory overhead. + * + * Adding a participant that is equal to a participant already registered will + * replace the scheduling rule associated with the participant, but will otherwise + * be ignored. + * + * @param participant The participant to add + * @param rule The scheduling rule that must be owned at the time the + * participant is called. This allows a participant to protect their data structures + * against access at unsafe times. + * + * @see #removeStringPoolParticipant(IStringPoolParticipant) + * @since 3.1 + */ + public void addStringPoolParticipant(IStringPoolParticipant participant, ISchedulingRule rule) { + participants.put(participant, rule); + if (getState() == Job.SLEEPING) + wakeUp(INITIAL_DELAY); + else + schedule(INITIAL_DELAY); + } + + /** + * Removes the indicated log listener from the set of registered string + * pool participants. If no such participant is registered, no action is taken. + * + * @param participant the participant to deregister + * @see #addStringPoolParticipant(IStringPoolParticipant, ISchedulingRule) + * @since 3.1 + */ + public void removeStringPoolParticipant(IStringPoolParticipant participant) { + participants.remove(participant); + } + + /* (non-Javadoc) + * Method declared on Job + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + //if the system is shutting down, don't build + if (systemBundle.getState() == Bundle.STOPPING) + return Status.OK_STATUS; + + //copy current participants to handle concurrent additions and removals to map + Map.Entry[] entries = participants.entrySet().toArray(new Map.Entry[participants.size()]); + ISchedulingRule[] rules = new ISchedulingRule[entries.length]; + IStringPoolParticipant[] toRun = new IStringPoolParticipant[entries.length]; + for (int i = 0; i < toRun.length; i++) { + toRun[i] = entries[i].getKey(); + rules[i] = entries[i].getValue(); + } + final ISchedulingRule rule = MultiRule.combine(rules); + long start = -1; + int savings = 0; + final IJobManager jobManager = Job.getJobManager(); + try { + jobManager.beginRule(rule, monitor); + start = System.currentTimeMillis(); + savings = shareStrings(toRun, monitor); + } finally { + jobManager.endRule(rule); + } + if (start > 0) { + lastDuration = System.currentTimeMillis() - start; + if (Policy.DEBUG_STRINGS) + Policy.debug("String sharing saved " + savings + " bytes in: " + lastDuration); //$NON-NLS-1$ //$NON-NLS-2$ + } + //throttle frequency if it takes too long + long scheduleDelay = Math.max(RESCHEDULE_DELAY, lastDuration * 100); + if (Policy.DEBUG_STRINGS) + Policy.debug("Rescheduling string sharing job in: " + scheduleDelay); //$NON-NLS-1$ + schedule(scheduleDelay); + return Status.OK_STATUS; + } + + private int shareStrings(IStringPoolParticipant[] toRun, IProgressMonitor monitor) { + final StringPool pool = new StringPool(); + for (int i = 0; i < toRun.length; i++) { + if (monitor.isCanceled()) + break; + final IStringPoolParticipant current = toRun[i]; + SafeRunner.run(new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + //exceptions are already logged, so nothing to do + } + + @Override + public void run() { + current.shareStrings(pool); + } + }); + } + return pool.getSavedStringCount(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java new file mode 100644 index 0000000000..ea964d7a1c --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java @@ -0,0 +1,357 @@ +/******************************************************************************* + * 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.utils; + +import java.io.*; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.util.GregorianCalendar; +import java.util.Random; +import org.eclipse.core.runtime.Assert; + +public class UniversalUniqueIdentifier implements java.io.Serializable { + + /** + * All serializable objects should have a stable serialVersionUID + */ + private static final long serialVersionUID = 1L; + + /* INSTANCE FIELDS =============================================== */ + + private byte[] fBits = new byte[BYTES_SIZE]; + + /* NON-FINAL PRIVATE STATIC FIELDS =============================== */ + + private static BigInteger fgPreviousClockValue; + private static int fgClockAdjustment = 0; + private static int fgClockSequence = -1; + private static byte[] nodeAddress; + + static { + nodeAddress = computeNodeAddress(); + } + + /* PRIVATE STATIC FINAL FIELDS =================================== */ + + private static Random fgRandomNumberGenerator = new Random(); + + /* PUBLIC STATIC FINAL FIELDS ==================================== */ + + public static final int BYTES_SIZE = 16; + public static final byte[] UNDEFINED_UUID_BYTES = new byte[16]; + public static final int MAX_CLOCK_SEQUENCE = 0x4000; + public static final int MAX_CLOCK_ADJUSTMENT = 0x7FFF; + public static final int TIME_FIELD_START = 0; + public static final int TIME_FIELD_STOP = 6; + public static final int TIME_HIGH_AND_VERSION = 7; + public static final int CLOCK_SEQUENCE_HIGH_AND_RESERVED = 8; + public static final int CLOCK_SEQUENCE_LOW = 9; + public static final int NODE_ADDRESS_START = 10; + public static final int NODE_ADDRESS_BYTE_SIZE = 6; + + public static final int BYTE_MASK = 0xFF; + + public static final int HIGH_NIBBLE_MASK = 0xF0; + + public static final int LOW_NIBBLE_MASK = 0x0F; + + public static final int SHIFT_NIBBLE = 4; + + public static final int ShiftByte = 8; + + /** + UniversalUniqueIdentifier default constructor returns a + new instance that has been initialized to a unique value. + */ + public UniversalUniqueIdentifier() { + this.setVersion(1); + this.setVariant(1); + this.setTimeValues(); + this.setNode(getNodeAddress()); + } + + /** + Constructor that accepts the bytes to use for the instance.   The format + of the byte array is compatible with the toBytes() method. + +

The constructor returns the undefined uuid if the byte array is invalid. + + @see #toBytes() + @see #BYTES_SIZE + */ + public UniversalUniqueIdentifier(byte[] byteValue) { + fBits = new byte[BYTES_SIZE]; + if (byteValue.length >= BYTES_SIZE) + System.arraycopy(byteValue, 0, fBits, 0, BYTES_SIZE); + } + + 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); + } + + private static BigInteger clockValueNow() { + GregorianCalendar now = new GregorianCalendar(); + BigInteger nowMillis = BigInteger.valueOf(now.getTime().getTime()); + BigInteger baseMillis = BigInteger.valueOf(now.getGregorianChange().getTime()); + + return (nowMillis.subtract(baseMillis).multiply(BigInteger.valueOf(10000L))); + } + + /** + Simply increases the visibility of Object's clone. + Otherwise, no new behaviour. + */ + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + Assert.isTrue(false, Messages.utils_clone); + return null; + } + } + + public static int compareTime(byte[] fBits1, byte[] fBits2) { + for (int i = TIME_FIELD_STOP; i >= 0; i--) + if (fBits1[i] != fBits2[i]) + return (0xFF & fBits1[i]) - (0xFF & fBits2[i]); + return 0; + } + + /** + * Answers the node address attempting to mask the IP + * address of this machine. + * + * @return byte[] the node address + */ + private static byte[] computeNodeAddress() { + + byte[] address = new byte[NODE_ADDRESS_BYTE_SIZE]; + + // Seed the secure randomizer with some oft-varying inputs + int thread = Thread.currentThread().hashCode(); + long time = System.currentTimeMillis(); + int objectId = System.identityHashCode(new String()); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(byteOut); + byte[] ipAddress = getIPAddress(); + + try { + if (ipAddress != null) + out.write(ipAddress); + out.write(thread); + out.writeLong(time); + out.write(objectId); + out.close(); + } catch (IOException exc) { + //ignore the failure, we're just trying to come up with a random seed + } + byte[] rand = byteOut.toByteArray(); + + SecureRandom randomizer = new SecureRandom(rand); + randomizer.nextBytes(address); + + // set the MSB of the first octet to 1 to distinguish from IEEE node addresses + address[0] = (byte) (address[0] | (byte) 0x80); + + return address; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof UniversalUniqueIdentifier)) + return false; + + byte[] other = ((UniversalUniqueIdentifier) obj).fBits; + if (fBits == other) + return true; + if (fBits.length != other.length) + return false; + for (int i = 0; i < fBits.length; i++) { + if (fBits[i] != other[i]) + return false; + } + return true; + } + + /** + Answers the IP address of the local machine using the + Java API class InetAddress. + + @return byte[] the network address in network order + @see java.net.InetAddress#getLocalHost() + @see java.net.InetAddress#getAddress() + */ + protected static byte[] getIPAddress() { + try { + return InetAddress.getLocalHost().getAddress(); + } catch (UnknownHostException e) { + //valid for this to be thrown be a machine with no IP connection + //It is VERY important NOT to throw this exception + return null; + } catch (ArrayIndexOutOfBoundsException e) { + // there appears to be a bug in the VM if there is an alias + // see bug 354820. As above it is important not to throw this + return null; + } + } + + private static byte[] getNodeAddress() { + return nodeAddress; + } + + @Override + public int hashCode() { + return fBits[0] + fBits[3] + fBits[7] + fBits[11] + fBits[15]; + } + + private static int nextClockSequence() { + + if (fgClockSequence == -1) + fgClockSequence = (int) (fgRandomNumberGenerator.nextDouble() * MAX_CLOCK_SEQUENCE); + + fgClockSequence = (fgClockSequence + 1) % MAX_CLOCK_SEQUENCE; + + return fgClockSequence; + } + + private static BigInteger nextTimestamp() { + + BigInteger timestamp = clockValueNow(); + int timestampComparison; + + timestampComparison = timestamp.compareTo(fgPreviousClockValue); + + if (timestampComparison == 0) { + if (fgClockAdjustment == MAX_CLOCK_ADJUSTMENT) { + while (timestamp.compareTo(fgPreviousClockValue) == 0) + timestamp = clockValueNow(); + timestamp = nextTimestamp(); + } else + fgClockAdjustment++; + } else { + fgClockAdjustment = 0; + + if (timestampComparison < 0) + nextClockSequence(); + } + + return timestamp; + } + + private void setClockSequence(int clockSeq) { + int clockSeqHigh = (clockSeq >>> ShiftByte) & LOW_NIBBLE_MASK; + int reserved = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & HIGH_NIBBLE_MASK; + + fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) (reserved | clockSeqHigh); + fBits[CLOCK_SEQUENCE_LOW] = (byte) (clockSeq & BYTE_MASK); + } + + protected void setNode(byte[] bytes) { + + for (int index = 0; index < NODE_ADDRESS_BYTE_SIZE; index++) + fBits[index + NODE_ADDRESS_START] = bytes[index]; + } + + private void setTimestamp(BigInteger timestamp) { + BigInteger value = timestamp; + BigInteger bigByte = BigInteger.valueOf(256L); + BigInteger[] results; + int version; + int timeHigh; + + for (int index = TIME_FIELD_START; index < TIME_FIELD_STOP; index++) { + results = value.divideAndRemainder(bigByte); + value = results[0]; + fBits[index] = (byte) results[1].intValue(); + } + version = fBits[TIME_HIGH_AND_VERSION] & HIGH_NIBBLE_MASK; + timeHigh = value.intValue() & LOW_NIBBLE_MASK; + fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | version); + } + + protected synchronized void setTimeValues() { + this.setTimestamp(timestamp()); + this.setClockSequence(fgClockSequence); + } + + protected int setVariant(int variantIdentifier) { + int clockSeqHigh = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & LOW_NIBBLE_MASK; + int variant = variantIdentifier & LOW_NIBBLE_MASK; + + fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) ((variant << SHIFT_NIBBLE) | clockSeqHigh); + return (variant); + } + + protected void setVersion(int versionIdentifier) { + int timeHigh = fBits[TIME_HIGH_AND_VERSION] & LOW_NIBBLE_MASK; + int version = versionIdentifier & LOW_NIBBLE_MASK; + + fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | (version << SHIFT_NIBBLE)); + } + + private static BigInteger timestamp() { + BigInteger timestamp; + + if (fgPreviousClockValue == null) { + fgClockAdjustment = 0; + nextClockSequence(); + timestamp = clockValueNow(); + } else + timestamp = nextTimestamp(); + + fgPreviousClockValue = timestamp; + return fgClockAdjustment == 0 ? timestamp : timestamp.add(BigInteger.valueOf(fgClockAdjustment)); + } + + /** + This representation is compatible with the (byte[]) constructor. + + @see #UniversalUniqueIdentifier(byte[]) + */ + public byte[] toBytes() { + byte[] result = new byte[fBits.length]; + + System.arraycopy(fBits, 0, result, 0, fBits.length); + return result; + } + + @Override + public String toString() { + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < fBits.length; i++) + appendByteString(buffer, fBits[i]); + return buffer.toString(); + } + + public String toStringAsBytes() { + String result = "{"; //$NON-NLS-1$ + + for (int i = 0; i < fBits.length; i++) { + result += fBits[i]; + if (i < fBits.length + 1) + result += ","; //$NON-NLS-1$ + } + return result + "}"; //$NON-NLS-1$ + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java new file mode 100644 index 0000000000..1d8e475408 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.utils; + +public class WrappedRuntimeException extends RuntimeException { + + /** + * All serializable objects should have a stable serialVersionUID + */ + private static final long serialVersionUID = 1L; + + private Throwable target; + + public WrappedRuntimeException(Throwable target) { + super(); + this.target = target; + } + + public Throwable getTargetException() { + return this.target; + } + + @Override + public String getMessage() { + return target.getMessage(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties new file mode 100644 index 0000000000..2e3ae9b1c5 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties @@ -0,0 +1,322 @@ +############################################################################### +# 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) - [252996] add resource filtering +# Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support +# Francis Lynch (Wind River) - [301563] Save and load tree snapshots +# Martin Oberhuber (Wind River) - [306575] Save snapshot location with project +############################################################################### +### Resources plugin messages. + +### dtree +dtree_immutable = Illegal attempt to modify an immutable tree. +dtree_malformedTree = Malformed tree. +dtree_missingChild = Missing child node: {0}. +dtree_notFound = Tree element ''{0}'' not found. +dtree_notImmutable = Tree must be immutable. +dtree_reverse = Tried to reverse a non-comparison tree. +dtree_subclassImplement = Subclass should have implemented this. +dtree_switchError = Switch error in DeltaTreeReader.readNode(). + +### events +events_builderError = Errors running builder ''{0}'' on project ''{1}''. +events_building_0 = Building workspace +events_building_1 = Building ''{0}'' +events_errors = Errors occurred during the build. +events_instantiate_1 = Error instantiating builder ''{0}''. +events_invoking_1 = Invoking builder on ''{0}''. +events_invoking_2 = Invoking ''{0}'' on ''{1}''. +events_skippingBuilder = Skipping builder ''{0}'' for project ''{1}''. Either the builder is missing from the install, or it belongs to a project nature that is missing or disabled. +events_unknown = {0} encountered while running {1}. + +history_copyToNull = Unable to copy local history to or from a null location. +history_copyToSelf = Unable to copy local history to and from the same location. +history_errorContentDescription = Error retrieving content description for local history for: ''{0}''. +history_notValid = State is not valid or might have expired. +history_problemsCleaning = Problems cleaning up history store. + +links_creating = Creating link. +links_errorLinkReconcile = Error processing changed links in project description file. +links_invalidLocation = ''{0}'' is not a valid location for linked resources. +links_localDoesNotExist = Cannot create linked resource. Local location ''{0}'' does not exist. +links_locationOverlapsLink = ''{0}'' is not a valid location because the project contains a linked resource at that location. +links_locationOverlapsProject = Cannot create a link to ''{0}'' because it overlaps the location of the project that contains the linked resource. +links_natureVeto = Linking is not allowed because project nature ''{0}'' does not allow it. +links_noPath = A Link location must be specified. +links_overlappingResource = Location ''{0}'' may overlap another resource. This can cause unexpected side-effects. +links_updatingDuplicate = Updating duplicate resource: ''{0}''. +links_parentNotAccessible = Cannot create linked resource ''{0}''. The parent resource is not accessible. +links_notFileFolder = Cannot create linked resource ''{0}''. Only files and folders can be linked. +links_vetoNature = Cannot add nature because project ''{0}'' contains linked resources, and nature ''{1}'' does not allow it. +links_workspaceVeto = Linked resources are not supported by this application. +links_wrongLocalType = Cannot create linked resource ''{0}''. Files cannot be linked to folders. +links_resourceIsNotALink=Resource ''{0}'' must be a linked resource to change its linked location. +links_setLocation=Changing link location. + +group_invalidParent = Only virtual folders and links can be created under a virtual folder. + +filters_missingFilterType= Missing resource filter type: ''{0}''. + +### local store +localstore_copying = Copying ''{0}''. +localstore_copyProblem = Problems encountered while copying resources. +localstore_couldnotDelete = Could not delete ''{0}''. +localstore_couldNotMove = Could not move ''{0}''. +localstore_couldNotRead = Could not read file ''{0}''. +localstore_couldNotWrite = Could not write file ''{0}''. +localstore_couldNotWriteReadOnly = Could not write to read-only file: ''{0}''. +localstore_deleteProblem = Problems encountered while deleting resources. +localstore_deleting = Deleting ''{0}''. +localstore_failedReadDuringWrite = Could not read from source when writing file ''{0}'' +localstore_fileExists = A resource already exists on disk ''{0}''. +localstore_fileNotFound = File not found: {0}. +localstore_locationUndefined = The location for ''{0}'' could not be determined because it is based on an undefined path variable. +localstore_refreshing = Refreshing ''{0}''. +localstore_refreshingRoot = Refreshing workspace. +localstore_resourceExists = Resource already exists on disk: ''{0}''. +localstore_resourceDoesNotExist = Resource does not exist on disk: ''{0}''. +localstore_resourceIsOutOfSync = Resource is out of sync with the file system: ''{0}''. + +### Resource mappings and models +mapping_invalidDef = Model provider extension found with invalid definition: {0}. +mapping_wrongType = Model provider ''{0}'' does not extend ModelProvider. +mapping_noIdentifier = Found model provider extension with no identifier; ignoring extension. +mapping_validate = Validating resource changes +mapping_multiProblems = Multiple potential side effects have been identified. + +### internal.resources +natures_duplicateNature = Duplicate nature: {0}. +natures_hasCycle = Nature is invalid because its prerequisites form a cycle: {0} +natures_missingIdentifier = Found nature extension with no identifier; ignoring extension. +natures_missingNature = Nature does not exist: {0}. +natures_missingPrerequisite = Nature {0} is missing prerequisite nature: {1}. +natures_multipleSetMembers = Multiple natures found for nature set: {0}. +natures_invalidDefinition = Nature extension found with invalid definition: {0}. +natures_invalidRemoval = Cannot remove nature {0} because it is a prerequisite of nature {1}. +natures_invalidSet = The set of natures is not valid. + +pathvar_length = Path variable name must have a length > 0. +pathvar_beginLetter = Path variable name must begin with a letter or underscore. +pathvar_invalidChar = Path variable name cannot contain character: {0}. +pathvar_invalidValue = Path variable value must be valid and absolute. +pathvar_undefined = ''{0}'' is not a valid location. The location is relative to undefined workspace path variable ''{1}''. +pathvar_whitespace= Path variable name cannot contain whitespace + +### preferences +preferences_deleteException=Exception deleting file: {0}. +preferences_loadException=Exception loading preferences from: {0}. +preferences_operationCanceled=Operation canceled. +preferences_removeNodeException=Exception while removing preference node: {0}. +preferences_clearNodeException=Exception while clearing preference node: {0}. +preferences_saveProblems=Exception occurred while saving project preferences: {0}. +preferences_syncException=Exception occurred while synchronizing node: {0}. + +projRead_badLinkName = Names ''{0}'' and ''{1}'' detected for a single link. Using ''{0}''. +projRead_badLinkType2 = Types ''{0}'' and ''{1}'' detected for a single link. Using ''{0}''. +projRead_badLocation = Locations ''{0}'' and ''{1}'' detected for a single link. Using ''{0}''. +projRead_emptyLinkName = Empty name detected for linked resource with type ''{0}'' and location ''{1}''. +projRead_badLinkType = Illegal link type \"-1\" detected for linked resource with name ''{0}'' and location ''{1}''. +projRead_badLinkLocation = Empty location detected for linked resource with name ''{0}'' and type ''{1}''. +projRead_whichKey = Two values detected for an argument name in a build command: ''{0}'' and ''{1}''. Using ''{0}''. +projRead_whichValue = Two values detected for an argument value in a build command: ''{0}'' and ''{1}''. Using ''{0}''. +projRead_notProjectDescription = Encountered element name ''{0}'' instead of \"project\" when trying to read a project description file. +projRead_badSnapshotLocation = Invalid resource snapshot location URI ''{0}'' is not absolute. +projRead_cannotReadSnapshot = Failed to read resource snapshot for project ''{0}'': {1} +projRead_failureReadingProjectDesc = Failure occurred reading .project file. +projRead_missingProjectName = Missing project name. + +projRead_emptyVariableName = Empty variable name detected in project "{0}\". + +projRead_badFilterName = Names ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. +projRead_emptyFilterName = Empty name detected for filtered resource with type ''{0}'' and id ''{1}''. +projRead_badFilterID = Empty filter id detected for filtered resource with name ''{0}'' and type ''{1}''. +projRead_badFilterType = Illegal filter type \"-1\" detected for filtered resource with name ''{0}'' and id ''{1}''. +projRead_badFilterType2 = Types ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. +projRead_badID = IDs ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. +projRead_badArguments= Arguments ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. + +properties_qualifierIsNull = Qualifier part of property key cannot be null. +properties_readProperties = Failure while reading persistent properties for resource ''{0}'', file was corrupt. Some properties may have been lost. +properties_valueTooLong = Could not set property: {0} {1}. Value is too long. +properties_couldNotClose = Could not close property store for: {0}. + +### auto-refresh +refresh_jobName = Refreshing workspace +refresh_task = Resources to refresh: {0} +refresh_pollJob = Searching for local changes +refresh_refreshErr = Problems occurred while refreshing local changes +refresh_installError = An error occurred while installing an auto-refresh monitor + +resources_cannotModify = The resource tree is locked for modifications. +resources_changeInAdd = Trying to change a marker in an add method. +resources_charsetBroadcasting = Reporting encoding changes. +resources_charsetUpdating = Updating encoding settings. +resources_closing_0 = Closing workspace. +resources_closing_1 = Closing ''{0}''. +resources_copyDestNotSub = Cannot copy ''{0}''. Destination should not be under source''s hierarchy. +resources_copying = Copying ''{0}''. +resources_copying_0 = Copying. +resources_copyNotMet = Copy requirements not met. +resources_copyProblem = Problems encountered while copying resources. +resources_couldnotDelete = Could not delete ''{0}''. +resources_create = Create. +resources_creating = Creating resource ''{0}''. +resources_deleteMeta = Could not delete metadata for ''{0}''. +resources_deleteProblem = Problems encountered while deleting resources. +resources_deleting = Deleting ''{0}''. +resources_deleting_0 = Deleting. +resources_destNotNull = Destination path should not be null. +resources_errorContentDescription = Error retrieving content description for resource ''{0}''. +resources_errorDeleting = Error deleting resource ''{0}'' from the workspace tree. +resources_errorMarkersDelete = Error deleting markers for resource ''{0}''. +resources_errorMarkersMove = Error moving markers from resource ''{0}'' to ''{1}''. +resources_wrongMarkerAttributeValueType = "The attribute value type is {0} and expected is one of java.lang.String, Boolean, Integer" +resources_errorMembers = Error retrieving members of container ''{0}''. +resources_errorMoving = Error moving resource ''{0}'' to ''{1}'' in the workspace tree. +resources_errorNature = Error configuring nature ''{0}''. +resources_errorPropertiesMove = Error moving properties for resource ''{0}'' to ''{1}''. +resources_errorRefresh = Errors occurred during refresh of resource ''{0}''. +resources_errorReadProject = Failed to read project description file from location ''{0}''. +resources_errorMultiRefresh = Errors occurred while refreshing resources with the local file system. +resources_errorValidator = Exception running validator code. +resources_errorVisiting = An error occurred while traversing resources. +resources_existsDifferentCase = A resource exists with a different case: ''{0}''. +resources_existsLocalDifferentCase = A resource exists on disk with a different case: ''{0}''. +resources_exMasterTable = Could not read master table. +resources_exReadProjectLocation = Could not read the project location for ''{0}''. +resources_exSafeRead = Could not read safe table. +resources_exSafeSave = Could not save safe table. +resources_exSaveMaster = Could not save master table to file ''{0}''. +resources_exSaveProjectLocation = Could not save the project location for ''{0}''. +resources_fileExists = A resource already exists on disk ''{0}''. +resources_fileToProj = Cannot copy a file to a project. +resources_flushingContentDescriptionCache = Flushing content description cache. +resources_folderOverFile = Cannot overwrite folder with file ''{0}''. +resources_format = Incompatible file format. Workspace was saved with an incompatible version: {0}. +resources_initValidator = Unable to instantiate validator. +resources_initHook = Unable to instantiate move/delete hook. +resources_initTeamHook = Unable to instantiate team hook. +resources_invalidCharInName = {0} is an invalid character in resource name ''{1}''. +resources_invalidCharInPath = {0} is an invalid character in path ''{1}''. +resources_invalidName = ''{0}'' is an invalid name on this platform. +resources_invalidPath = ''{0}'' is an invalid resource path. +resources_invalidProjDesc = Invalid project description. +resources_invalidResourceName = ''{0}'' is an invalid resource name. +resources_invalidRoot = Root (/) is an invalid resource path. +resources_markerNotFound = Marker id {0} not found. +resources_missingProjectMeta = The project description file (.project) for ''{0}'' is missing. This file contains important information about the project. The project will not function properly until this file is restored. +resources_missingProjectMetaRepaired = The project description file (.project) for ''{0}'' was missing. This file contains important information about the project. A new project description file has been created, but some information about the project may have been lost. +resources_moveDestNotSub = Cannot move ''{0}''. Destination should not be under source''s hierarchy. +resources_moveMeta = Error moving metadata area from {0} to {1}. +resources_moveNotMet = Move requirements not met. +resources_moveNotProject = Cannot move ''{0}'' to ''{1}''. Source must be a project. +resources_moveProblem = Problems encountered while moving resources. +resources_moveRoot = Cannot move the workspace root. +resources_moving = Moving ''{0}''. +resources_moving_0 = Moving. +resources_mustBeAbsolute = Path ''{0}'' must be absolute. +resources_mustBeLocal = Resource ''{0}'' is not local. +resources_mustBeOpen = Resource ''{0}'' is not open. +resources_mustExist = Resource ''{0}'' does not exist. +resources_mustNotExist = Resource ''{0}'' already exists. +resources_nameEmpty = Name cannot be empty. +resources_nameNull = Name must not be null. +resources_natureClass = Missing project nature class for ''{0}''. +resources_natureDeconfig = Error deconfiguring nature: {0}. +resources_natureExtension = Missing project nature extension for {0}. +resources_natureFormat = Project nature {0} does not specify a runtime attribute. +resources_natureImplement = Project nature {0} does not implement IProjectNature. +resources_notChild = Resource ''{0}'' is not a child of ''{1}''. +resources_oneValidator = There must be exactly 0 or 1 validator extensions defined in the fileModificationValidator extension point. +resources_oneHook = There must be exactly 0 or 1 hook extensions defined in the moveDeleteHook extension point. +resources_oneTeamHook = There must be exactly 0 or 1 hook extensions defined in the teamHook extension point. +resources_opening_1 = Opening ''{0}''. +resources_overlapWorkspace = {0} overlaps the workspace location: {1} +resources_overlapProject = {0} overlaps the location of another project: ''{1}'' +resources_pathNull = Paths must not be null. +resources_projectDesc = Problems encountered while setting project description. +resources_projectDescSync = Could not set the project description for ''{0}'' because the project description file (.project) is out of sync with the file system. +resources_projectMustNotBeOpen = Project must not be open. +resources_projectPath = Path for project must have only one segment. +resources_pruningHistory = Pruning history. +resources_reading = Reading. +resources_readingSnap = Reading snapshot. +resources_readingEncoding = Could not read encoding settings. +resources_readMarkers = Failure while reading markers, the marker file was corrupt. Some markers may be lost. +resources_readMeta = Could not read metadata for ''{0}''. +resources_readMetaWrongVersion = Could not read metadata for ''{0}''. Unexpected version: {1}. +resources_readOnly = Resource ''{0}'' is read-only. +resources_readOnly2 = Cannot edit read-only resources. +resources_readProjectMeta = Failed to read the project description file (.project) for ''{0}''. The file has been changed on disk, and it now contains invalid information. The project will not function properly until the description file is restored to a valid state. +resources_readProjectTree = Problems reading project tree. +resources_readSync = Errors reading sync info file: {0}. +resources_readWorkspaceMeta = Could not read workspace metadata. +resources_readWorkspaceMetaValue = Invalid attribute value in workspace metadata: {0}. Value will be ignored. +resources_readWorkspaceSnap = Problems reading workspace tree snapshot. +resources_readWorkspaceTree = Problems reading workspace tree. +resources_refreshing = Refreshing ''{0}''. +resources_refreshingRoot = Refreshing workspace. +resources_resetMarkers = Could not reset markers snapshot file. +resources_resetSync = Could not reset sync info snapshot file. +resources_resourcePath = Invalid path for resource ''{0}''. Must include project and resource name. +resources_saveOp = Save cannot be called from inside an operation. +resources_saveProblem = Problems occurred during save. +resources_saveWarnings = Save operation warnings. +resources_saving_0 = Saving workspace. +resources_savingEncoding = Could not save encoding settings. +resources_setDesc = Setting project description. +resources_setLocal = Setting resource local flag. +resources_settingCharset = Setting character set for resource ''{0}''. +resources_settingDefaultCharsetContainer = Setting default character set for resource ''{0}''. +resources_settingContents = Setting contents for ''{0}''. +resources_settingDerivedFlag = Setting derived flag for resource ''{0}''. +resources_shutdown = Workspace was not properly initialized or has already shutdown. +resources_shutdownProblems = Problem on shutdown. +resources_snapInit = Could not initialize snapshot file. +resources_snapRead = Could not read snapshot file. +resources_snapRequest = Snapshot requested. +resources_snapshot = Periodic workspace save. +resources_startupProblems = Workspace restored, but some problems occurred. +resources_touch = Touching resource ''{0}''. +resources_updating = Updating workspace +resources_updatingEncoding = Problems encountered while updating encoding settings. +resources_workspaceClosed = Workspace is closed. +resources_workspaceOpen = The workspace is already open. +resources_writeMeta = Could not write metadata for ''{0}''. +resources_writeWorkspaceMeta = Could not write workspace metadata ''{0}''. +resources_errorResourceIsFiltered=The resource will be filtered out by its parent resource filters + +synchronizer_partnerNotRegistered = Sync partner: {0} not registered with the synchronizer. + +### URL +url_badVariant = Unsupported \"platform:\" protocol variation {0}. +url_couldNotResolve_projectDoesNotExist = Project ''{0}'' does not exist. Could not resolve URL: {1}. +url_couldNotResolve_URLProtocolHandlerCanNotResolveURL = A protocol handler does not exist or can not resolve URL ''{0}'' into URL with file protocol. Could not resolve URL: {1}. +url_couldNotResolve_resourceLocationCanNotBeDetermined = Resource location ''{0}'' can not be determined. Could not resolve URL: {1}. + +### utils +utils_clone = Clone not supported. +utils_stringJobName = Compacting resource model + +### watson +watson_elementNotFound = Element not found: {0}. +watson_illegalSubtree = Illegal subtree passed to createSubtree(). +watson_immutable = Attempt to modify an immutable tree. +watson_noModify = Cannot modify implicit root node. +watson_nullArg = Null argument to {0}. +watson_unknown = Unknown format. + +### auto-refresh win32 native +WM_beginTask = finding out of sync resources +WM_jobName = Win32 refresh daemon +WM_errors = Problems occurred refreshing resources +WM_nativeErr = Problem occurred in auto-refresh native code: {0}. +WM_errCloseHandle = Problem closing native refresh handle: {0}. +WM_errCreateHandle = Problem creating handle for {0}, code: {0}. +WM_errFindChange = Problem finding next change, code: {0} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java new file mode 100644 index 0000000000..dc72914cad --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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.watson; + +/** + * This is what you would expect for an element tree comparator. + * Clients of the element tree that want specific comparison behaviour + * must define their own element comparator (without subclassing or + * otherwise extending this comparator). Internal element tree operations + * rely on the behaviour of this type, and the ElementTree maintainer + * reserves the right to change its behaviour as necessary. + */ +public final class DefaultElementComparator implements IElementComparator { + private static DefaultElementComparator singleton; + + /** + * Force clients to use the singleton + */ + protected DefaultElementComparator() { + super(); + } + + /** + * Returns the type of change. + */ + @Override + public int compare(Object oldInfo, Object newInfo) { + if (oldInfo == null && newInfo == null) + return 0; + if (oldInfo == null || newInfo == null) + return 1; + return testEquality(oldInfo, newInfo) ? 0 : 1; + } + + /** + * Returns the singleton instance + */ + public static IElementComparator getComparator() { + if (singleton == null) { + singleton = new DefaultElementComparator(); + } + return singleton; + } + + /** + * Makes a comparison based on equality + */ + protected boolean testEquality(Object oldInfo, Object newInfo) { + if (oldInfo == null && newInfo == null) + return true; + if (oldInfo == null || newInfo == null) + return false; + + return oldInfo.equals(newInfo); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java new file mode 100644 index 0000000000..025767e83f --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java @@ -0,0 +1,731 @@ +/******************************************************************************* + * 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.watson; + +import java.util.HashMap; +import org.eclipse.core.internal.dtree.*; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.StringPool; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * An ElementTree can be viewed as a generic rooted tree that stores + * a hierarchy of elements. An element in the tree consists of a + * (name, data, children) 3-tuple. The name can be any String, and + * the data can be any Object. The children are a collection of zero + * or more elements that logically fall below their parent in the tree. + * The implementation makes no guarantees about the ordering of children. + * + * Elements in the tree are referenced by a key that consists of the names + * of all elements on the path from the root to that element in the tree. + * For example, if root node "a" has child "b", which has child "c", element + * "c" can be referenced in the tree using the key (/a/b/c). Keys are represented + * using IPath objects, where the Paths are relative to the root element of the + * tree. + * @see IPath + * + * Each ElementTree has a single root element that is created implicitly and + * is always present in any tree. This root corresponds to the key (/), + * or the singleton Path.ROOT. The root element cannot be created + * or deleted, and its data and name cannot be set. The root element's children + * however can be modified (added, deleted, etc). The root path can be obtained + * using the getRoot() method. + * + * ElementTrees are modified in generations. The method newEmptyDelta() + * returns a new tree generation that can be modified arbitrarily by the user. + * For the purpose of explanation, we call such a tree "active". + * When the method immutable() is called, that tree generation is + * frozen, and can never again be modified. A tree must be immutable before + * a new tree generation can start. Since all ancestor trees are immutable, + * different active trees can have ancestors in common without fear of + * thread corruption problems. + * + * Internally, any single tree generation is simply stored as the + * set of changes between itself and its most recent ancestor (its parent). + * This compact delta representation allows chains of element trees to + * be created at relatively low cost. Clients of the ElementTree can + * instantaneously "undo" sets of changes by navigating up to the parent + * tree using the getParent() method. + * + * Although the delta representation is compact, extremely long delta + * chains make for a large structure that is potentially slow to query. + * For this reason, the client is encouraged to minimize delta chain + * lengths using the collapsing(int) and makeComplete() + * methods. The getDeltaDepth() method can be used to + * discover the length of the delta chain. The entire delta chain can + * also be re-oriented in terms of the current element tree using the + * reroot() operation. + * + * Classes are also available for tree serialization and navigation. + * @see ElementTreeReader + * @see ElementTreeWriter + * @see ElementTreeIterator + * + * Finally, why are ElementTrees in a package called "watson"? + * - "It's ElementTree my dear Watson, ElementTree." + */ +public class ElementTree { + protected DeltaDataTree tree; + protected IElementTreeData userData; + + private class ChildIDsCache { + ChildIDsCache(IPath path, IPath[] childPaths) { + this.path = path; + this.childPaths = childPaths; + } + + IPath path; + IPath[] childPaths; + } + + private volatile ChildIDsCache childIDsCache = null; + + private volatile DataTreeLookup lookupCache = null; + + private volatile DataTreeLookup lookupCacheIgnoreCase = null; + + private static int treeCounter = 0; + private int treeStamp; + + /** + * Creates a new empty element tree. + */ + public ElementTree() { + initialize(new DeltaDataTree()); + } + + /** + * Creates an element tree given its internal node representation. + */ + protected ElementTree(DataTreeNode rootNode) { + initialize(rootNode); + } + + /** + * Creates a new element tree with the given data tree as its representation. + */ + protected ElementTree(DeltaDataTree tree) { + initialize(tree); + } + + /** + * Creates a new empty delta element tree having the + * given tree as its parent. + */ + protected ElementTree(ElementTree parent) { + if (!parent.isImmutable()) { + parent.immutable(); + } + + /* copy the user data forward */ + IElementTreeData data = parent.getTreeData(); + if (data != null) { + userData = (IElementTreeData) data.clone(); + } + + initialize(parent.tree.newEmptyDeltaTree()); + } + + /** + * 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 + * element trees created by newEmptyDelta()/immutable(). + * + *

This element tree must be immutable at the start of this operation, + * and will be immutable afterwards. + * @return this tree. + */ + public synchronized ElementTree collapseTo(ElementTree parent) { + Assert.isTrue(tree.isImmutable()); + if (this == parent) { + //already collapsed + return this; + } + //collapse my tree to be a forward delta of the parent's tree. + tree.collapseTo(parent.tree, DefaultElementComparator.getComparator()); + return this; + } + + /** + * Creates the indicated element and sets its element info. + * The parent element must be present, otherwise an IllegalArgumentException + * is thrown. If the indicated element is already present in the tree, + * its element info is replaced and any existing children are + * deleted. + * + * @param key element key + * @param data element data, or null + */ + public synchronized void createElement(IPath key, Object data) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return; + + // Clear the child IDs cache in case it's referring to this parent. This is conservative. + childIDsCache = null; + + IPath parent = key.removeLastSegments(1); + try { + tree.createChild(parent, key.lastSegment(), data); + } catch (ObjectNotFoundException e) { + elementNotFound(parent); + } + // Set the lookup to be this newly created object. + lookupCache = DataTreeLookup.newLookup(key, true, data, true); + lookupCacheIgnoreCase = null; + } + + /** + * Creates or replaces the subtree below the given path with + * the given tree. The subtree can only have one child below + * the root, which will become the node specified by the given + * key in this tree. + * + * @param key The path of the new subtree in this tree. + * @see #getSubtree(IPath) + */ + public synchronized void createSubtree(IPath key, ElementTree subtree) { + /* don't allow creating subtrees at the root */ + if (key.isRoot()) { + throw new IllegalArgumentException(Messages.watson_noModify); + } + + // Clear the child IDs cache in case it's referring to this parent. + // This is conservative. + childIDsCache = null; + // Clear the lookup cache, in case the element being created is the same + // as for the last lookup. + lookupCache = lookupCacheIgnoreCase = null; + try { + /* don't copy the implicit root node of the subtree */ + IPath[] children = subtree.getChildren(subtree.getRoot()); + if (children.length != 1) { + throw new IllegalArgumentException(Messages.watson_illegalSubtree); + } + + /* get the subtree for the specified key */ + DataTreeNode node = (DataTreeNode) subtree.tree.copyCompleteSubtree(children[0]); + + /* insert the subtree in this tree */ + tree.createSubtree(key, node); + + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + + /** + * Deletes the indicated element and its descendents. + * The element must be present. + */ + public synchronized void deleteElement(IPath key) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return; + + // Clear the child IDs cache in case it's referring to this parent. + // This is conservative. + childIDsCache = null; + // Clear the lookup cache, in case the element being deleted is the same + // as for the last lookup. + lookupCache = lookupCacheIgnoreCase = null; + try { + tree.deleteChild(key.removeLastSegments(1), key.lastSegment()); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + + /** + * Complains that an element was not found + */ + protected void elementNotFound(IPath key) { + throw new IllegalArgumentException(NLS.bind(Messages.watson_elementNotFound, key)); + } + + /** + * Given an array of element trees, returns the index of the + * oldest tree. The oldest tree is the tree such that no + * other tree in the array is a descendent of that tree. + * Note that this counter-intuitive concept of oldest is based on the + * ElementTree orientation such that the complete tree is always the + * newest tree. + */ + public static int findOldest(ElementTree[] trees) { + + /* first put all the trees in a hashtable */ + HashMap candidates = new HashMap((int) (trees.length * 1.5 + 1)); + for (int i = 0; i < trees.length; i++) { + candidates.put(trees[i], trees[i]); + } + + /* keep removing parents until only one tree remains */ + ElementTree oldestSoFar = null; + while (candidates.size() > 0) { + /* get a new candidate */ + ElementTree current = candidates.values().iterator().next(); + + /* remove this candidate from the table */ + candidates.remove(current); + + /* remove all of this element's parents from the list of candidates*/ + ElementTree parent = current.getParent(); + + /* walk up chain until we hit the root or a tree we have already tested */ + while (parent != null && parent != oldestSoFar) { + candidates.remove(parent); + parent = parent.getParent(); + } + + /* the current candidate is the oldest tree seen so far */ + oldestSoFar = current; + + /* if the table is now empty, we have a winner */ + } + Assert.isNotNull(oldestSoFar); + + /* return the appropriate index */ + for (int i = 0; i < trees.length; i++) { + if (trees[i] == oldestSoFar) { + return i; + } + } + Assert.isTrue(false, "Should not get here"); //$NON-NLS-1$ + return -1; + } + + /** + * Returns the number of children of the element + * specified by the given path. + * The given element must be present in this tree. + */ + public synchronized int getChildCount(IPath key) { + Assert.isNotNull(key); + return getChildIDs(key).length; + } + + /** + * Returns the IDs of the children of the specified element. + * If the specified element is null, returns the root element path. + */ + protected IPath[] getChildIDs(IPath key) { + ChildIDsCache cache = childIDsCache; // Grab it in case it's replaced concurrently. + if (cache != null && cache.path == key) { + return cache.childPaths; + } + try { + if (key == null) + return new IPath[] {tree.rootKey()}; + IPath[] children = tree.getChildren(key); + childIDsCache = new ChildIDsCache(key, children); // Cache the result + return children; + } catch (ObjectNotFoundException e) { + elementNotFound(key); + return null; // can't get here + } + } + + /** + * Returns the paths of the children of the element + * specified by the given path. + * The given element must be present in this tree. + */ + public synchronized IPath[] getChildren(IPath key) { + Assert.isNotNull(key); + return getChildIDs(key); + } + + /** + * Returns the internal data tree. + */ + public DeltaDataTree getDataTree() { + return tree; + } + + /** + * Returns the element data for the given element identifier. + * The given element must be present in this tree. + */ + public synchronized Object getElementData(IPath key) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return null; + DataTreeLookup lookup = lookupCache; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) + lookupCache = lookup = tree.lookup(key); + if (lookup.isPresent) + return lookup.data; + elementNotFound(key); + return null; // can't get here + } + + /** + * Returns the element data for the given element identifier. + * The given element must be present in this tree. + */ + public synchronized Object getElementDataIgnoreCase(IPath key) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return null; + DataTreeLookup lookup = lookupCacheIgnoreCase; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) + lookupCacheIgnoreCase = lookup = tree.lookupIgnoreCase(key); + if (lookup.isPresent) + return lookup.data; + elementNotFound(key); + return null; // can't get here + } + + /** + * Returns the names of the children of the specified element. + * The specified element must exist in the tree. + * If the specified element is null, returns the root element path. + */ + public synchronized String[] getNamesOfChildren(IPath key) { + try { + if (key == null) + return new String[] {""}; //$NON-NLS-1$ + return tree.getNamesOfChildren(key); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + return null; // can't get here + } + } + + /** + * Returns the parent tree, or null if there is no parent. + */ + public ElementTree getParent() { + DeltaDataTree parentTree = tree.getParent(); + if (parentTree == null) { + return null; + } + // The parent ElementTree is stored as the node data of the parent DeltaDataTree, + // to simplify canonicalization in the presence of rerooting. + return (ElementTree) parentTree.getData(tree.rootKey()); + } + + /** + * Returns the root node of this tree. + */ + public IPath getRoot() { + return getChildIDs(null)[0]; + } + + /** + * Returns the subtree rooted at the given key. In the resulting tree, + * the implicit root node (designated by Path.ROOT), has a single child, + * which is the node specified by the given key in this tree. + * + * The subtree must be present in this tree. + * + * @see #createSubtree(IPath, ElementTree) + */ + public ElementTree getSubtree(IPath key) { + /* the subtree of the root of this tree is just this tree */ + if (key.isRoot()) { + return this; + } + try { + DataTreeNode elementNode = (DataTreeNode) tree.copyCompleteSubtree(key); + return new ElementTree(elementNode); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + return null; + } + } + + /** + * Returns the user data associated with this tree. + */ + public IElementTreeData getTreeData() { + return userData; + } + + /** + * Returns true if there have been changes in the tree between the two + * given layers. The two must be related and new must be newer than old. + * That is, new must be an ancestor of old. + */ + public static boolean hasChanges(ElementTree newLayer, ElementTree oldLayer, IElementComparator comparator, boolean inclusive) { + // if any of the layers are null, assume that things have changed + if (newLayer == null || oldLayer == null) + return true; + if (newLayer == oldLayer) + return false; + //if the tree data has changed, then the tree has changed + if (comparator.compare(newLayer.getTreeData(), oldLayer.getTreeData()) != IElementComparator.K_NO_CHANGE) + return true; + + // The tree structure has the top layer(s) (i.e., tree) parentage pointing down to a complete + // layer whose parent is null. The bottom layers (i.e., operationTree) point up to the + // common complete layer whose parent is null. The complete layer moves up as + // changes happen. To see if any changes have happened, we should consider only + // layers whose parent is not null. That is, skip the complete layer as it will clearly not be + // empty. + + // look down from the current layer (always inclusive) if the top layer is mutable + ElementTree stopLayer = null; + if (newLayer.isImmutable()) + // if the newLayer is immutable, the tree structure all points up so ensure that + // when searching up, we stop at newLayer (inclusive) + stopLayer = newLayer.getParent(); + else { + ElementTree layer = newLayer; + while (layer != null && layer.getParent() != null) { + if (!layer.getDataTree().isEmptyDelta()) + return true; + layer = layer.getParent(); + } + } + + // look up from the layer at which we started to null or newLayer's parent (variably inclusive) + // depending on whether newLayer is mutable. + ElementTree layer = inclusive ? oldLayer : oldLayer.getParent(); + while (layer != null && layer.getParent() != stopLayer) { + if (!layer.getDataTree().isEmptyDelta()) + return true; + layer = layer.getParent(); + } + // didn't find anything that changed + return false; + } + + /** + * Makes this tree immutable (read-only); ignored if it is already + * immutable. + */ + public synchronized void immutable() { + if (!tree.isImmutable()) { + tree.immutable(); + /* need to clear the lookup cache since it reports whether results were found + in the topmost delta, and the order of deltas is changing */ + lookupCache = lookupCacheIgnoreCase = null; + /* reroot the delta chain at this tree */ + tree.reroot(); + } + } + + /** + * Returns true if this element tree includes an element with the given + * key, false otherwise. + */ + public synchronized boolean includes(IPath key) { + DataTreeLookup lookup = lookupCache; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) { + lookupCache = lookup = tree.lookup(key); + } + return lookup.isPresent; + } + + /** + * Returns true if this element tree includes an element with the given + * key, ignoring the case of the key, and false otherwise. + */ + public boolean includesIgnoreCase(IPath key) { + DataTreeLookup lookup = lookupCacheIgnoreCase; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) { + lookupCacheIgnoreCase = lookup = tree.lookupIgnoreCase(key); + } + return lookup.isPresent; + } + + protected void initialize(DataTreeNode rootNode) { + /* create the implicit root node */ + initialize(new DeltaDataTree(new DataTreeNode(null, null, new AbstractDataTreeNode[] {rootNode}))); + } + + protected void initialize(DeltaDataTree newTree) { + // Keep this element tree as the data of the root node. + // Useful for canonical results for ElementTree.getParent(). + // see getParent(). + treeStamp = treeCounter++; + newTree.setData(newTree.rootKey(), this); + this.tree = newTree; + } + + /** + * Returns whether this tree is immutable. + */ + public boolean isImmutable() { + return tree.isImmutable(); + } + + /** + * Merges a chain of deltas for a certain subtree to this tree. + * If this tree has any data in the specified subtree, it will + * be overwritten. The receiver tree must be open, and it will + * be made immutable during the merge operation. The trees in the + * provided array will be replaced by new trees that have been + * merged into the receiver's delta chain. + * + * @param path The path of the subtree chain to merge + * @param trees The chain of trees to merge. The trees can be + * in any order, but they must all form a simple ancestral chain. + * @return A new open tree with the delta chain merged in. + */ + public ElementTree mergeDeltaChain(IPath path, ElementTree[] trees) { + if (path == null || trees == null) { + throw new IllegalArgumentException(NLS.bind(Messages.watson_nullArg, "ElementTree.mergeDeltaChain")); //$NON-NLS-1$ + } + + /* The tree has to be open */ + if (isImmutable()) { + throw new IllegalArgumentException(Messages.watson_immutable); + } + ElementTree current = this; + if (trees.length > 0) { + /* find the oldest tree to be merged */ + ElementTree toMerge = trees[findOldest(trees)]; + + /* merge the trees from oldest to newest */ + while (toMerge != null) { + if (path.isRoot()) { + //copy all the children + IPath[] children = toMerge.getChildren(Path.ROOT); + for (int i = 0; i < children.length; i++) { + current.createSubtree(children[i], toMerge.getSubtree(children[i])); + } + } else { + //just copy the specified node + current.createSubtree(path, toMerge.getSubtree(path)); + } + current.immutable(); + + /* replace the tree in the array */ + /* we have to go through all trees because there may be duplicates */ + for (int i = 0; i < trees.length; i++) { + if (trees[i] == toMerge) { + trees[i] = current; + } + } + current = current.newEmptyDelta(); + toMerge = toMerge.getParent(); + } + } + return current; + } + + /** + * Creates a new element tree which is represented as a delta on this one. + * Initially they have the same content. Subsequent changes to the new + * tree will not affect this one. + */ + public synchronized ElementTree newEmptyDelta() { + // Don't want old trees hanging onto cached infos. + lookupCache = lookupCacheIgnoreCase = null; + return new ElementTree(this); + } + + /** + * Returns a mutable copy of the element data for the given path. + * This copy will be held onto in the most recent delta. + * ElementTree data MUST implement the IElementTreeData interface + * for this method to work. If the data does not define that interface + * this method will fail. + */ + public synchronized Object openElementData(IPath key) { + Assert.isTrue(!isImmutable()); + + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return null; + DataTreeLookup lookup = lookupCache; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) { + lookupCache = lookup = tree.lookup(key); + } + if (lookup.isPresent) { + if (lookup.foundInFirstDelta) + return lookup.data; + /** + * The node has no data in the most recent delta. + * Pull it up to the present delta by setting its data with a clone. + */ + IElementTreeData oldData = (IElementTreeData) lookup.data; + if (oldData != null) { + try { + Object newData = oldData.clone(); + tree.setData(key, newData); + lookupCache = lookupCacheIgnoreCase = null; + return newData; + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + } else { + elementNotFound(key); + } + return null; + } + + /** + * Sets the element for the given element identifier. + * The given element must be present in this tree. + * @param key element identifier + * @param data element info, or null + */ + public synchronized void setElementData(IPath key, Object data) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return; + + Assert.isNotNull(key); + // Clear the lookup cache, in case the element being modified is the same + // as for the last lookup. + lookupCache = lookupCacheIgnoreCase = null; + try { + tree.setData(key, data); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + + /** + * Sets the user data associated with this tree. + */ + public void setTreeData(IElementTreeData data) { + userData = data; + } + + /* (non-Javadoc) + * Method declared on IStringPoolParticipant + */ + public void shareStrings(StringPool set) { + tree.storeStrings(set); + } + + /** + * Returns a string representation of this element tree's + * structure suitable for debug purposes. + */ + public String toDebugString() { + final StringBuffer buffer = new StringBuffer("\n"); //$NON-NLS-1$ + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree aTree, IPathRequestor elementID, Object elementContents) { + buffer.append(elementID.requestPath() + " " + elementContents + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + return true; + } + }; + new ElementTreeIterator(this, Path.ROOT).iterate(visitor); + return buffer.toString(); + } + + @Override + public String toString() { + return "ElementTree(" + treeStamp + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java new file mode 100644 index 0000000000..e433df07b5 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * 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.watson; + +import org.eclipse.core.internal.dtree.AbstractDataTreeNode; +import org.eclipse.core.internal.dtree.DataTreeNode; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; + +/** + * A class for performing operations on each element in an element tree. + * For example, this can be used to print the contents of a tree. + *

+ * When creating an ElementTree iterator, an element tree and root path must be + * supplied. When the iterate() method is called, a visitor object + * must be provided. The visitor is called once for each node of the tree. For + * each node, the visitor is passed the entire tree, the object in the tree at + * that node, and a callback for requesting the full path of that node. + *

+ * Example: +

+ // printing a crude representation of the poster child
+ IElementContentVisitor visitor=
+     new IElementContentVisitor() {
+   public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
+     System.out.println(requestor.requestPath() + " -> " + elementContents);
+     return true;
+   }
+ });
+ ElementTreeIterator iterator = new ElementTreeIterator(tree, Path.ROOT);
+ iterator.iterate(visitor);
+ 
+ */ +public class ElementTreeIterator implements IPathRequestor { + //for path requestor + private String[] segments = new String[10]; + private int nextFreeSegment; + + /* the tree being visited */ + private ElementTree tree; + + /* the root of the subtree to visit */ + private IPath path; + + /* the immutable data tree being visited */ + private DataTreeNode treeRoot; + + /** + * Creates a new element tree iterator for visiting the given tree starting + * at the given path. + */ + public ElementTreeIterator(ElementTree tree, IPath path) { + this.tree = tree; + this.path = path; + //treeRoot can be null if deleted concurrently + //must copy the tree while owning the tree's monitor to prevent concurrent deletion while creating visitor's copy + synchronized (tree) { + treeRoot = (DataTreeNode) tree.getDataTree().safeCopyCompleteSubtree(path); + } + } + + /** + * Iterates through the given element tree and visit each element (node) + * passing in the element's ID and element object. + */ + private void doIteration(DataTreeNode node, IElementContentVisitor visitor) { + //push the name of this node to the requestor stack + if (nextFreeSegment >= segments.length) { + grow(); + } + segments[nextFreeSegment++] = node.getName(); + + //do the visit + if (visitor.visitElement(tree, this, node.getData())) { + //recurse + AbstractDataTreeNode[] children = node.getChildren(); + int len = children.length; + for (int i = 0; i < len; i++) { + doIteration((DataTreeNode) children[i], visitor); + } + } + + //pop the segment from the requestor stack + nextFreeSegment--; + if (nextFreeSegment < 0) + nextFreeSegment = 0; + } + + /** + * Method grow. + */ + private void grow() { + //grow the segments array + int oldLen = segments.length; + String[] newPaths = new String[oldLen * 2]; + System.arraycopy(segments, 0, newPaths, 0, oldLen); + segments = newPaths; + } + + /** + * Iterates through this iterator's tree and visits each element in the + * subtree rooted at the given path. The visitor is passed each element's + * data and a request callback for obtaining the path. + */ + public void iterate(IElementContentVisitor visitor) { + if (path.isRoot()) { + //special visit for root element to use special treeData + if (visitor.visitElement(tree, this, tree.getTreeData())) { + if (treeRoot == null) + return; + AbstractDataTreeNode[] children = treeRoot.getChildren(); + int len = children.length; + for (int i = 0; i < len; i++) { + doIteration((DataTreeNode) children[i], visitor); + } + } + } else { + if (treeRoot == null) + return; + push(path, path.segmentCount() - 1); + doIteration(treeRoot, visitor); + } + } + + /** + * Push the first "toPush" segments of this path. + */ + private void push(IPath pathToPush, int toPush) { + if (toPush <= 0) + return; + for (int i = 0; i < toPush; i++) { + if (nextFreeSegment >= segments.length) { + grow(); + } + segments[nextFreeSegment++] = pathToPush.segment(i); + } + } + + @Override + public String requestName() { + if (nextFreeSegment == 0) + return ""; //$NON-NLS-1$ + return segments[nextFreeSegment - 1]; + } + + @Override + public IPath requestPath() { + if (nextFreeSegment == 0) + return Path.ROOT; + int length = nextFreeSegment; + for (int i = 0; i < nextFreeSegment; i++) { + length += segments[i].length(); + } + StringBuffer pathBuf = new StringBuffer(length); + for (int i = 0; i < nextFreeSegment; i++) { + pathBuf.append('/'); + pathBuf.append(segments[i]); + } + return new Path(null, pathBuf.toString()); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.java new file mode 100644 index 0000000000..044f193859 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.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 + * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +import java.io.*; +import org.eclipse.core.internal.dtree.DataTreeReader; +import org.eclipse.core.internal.dtree.IDataFlattener; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.runtime.*; + +/** ElementTreeReader is the standard implementation + * of an element tree serialization reader. + * + *

Subclasses of this reader read can handle current and various + * known old formats of a saved element tree, and dispatch internally + * to an appropriate reader. + * + *

The reader has an IElementInfoFactory, + * which it consults for the schema and for creating + * and reading element infos. + * + *

Element tree readers are thread-safe; several + * threads may share a single reader provided, of course, + * that the IElementInfoFactory is thread-safe. + */ +public class ElementTreeReader { + /** The element info factory. + */ + protected IElementInfoFlattener elementInfoFlattener; + + /** + * For reading and writing delta trees + */ + protected DataTreeReader dataTreeReader; + + /** + * Constructs a new element tree reader that works for + * the given element info flattener. + */ + public ElementTreeReader(final IElementInfoFlattener factory) { + Assert.isNotNull(factory); + elementInfoFlattener = factory; + + /* wrap the IElementInfoFlattener in an IDataFlattener */ + IDataFlattener f = new IDataFlattener() { + @Override + public void writeData(IPath path, Object data, DataOutput output) { + //not needed + } + + @Override + public Object readData(IPath path, DataInput input) throws IOException { + //never read the root node of an ElementTree + //this node is reserved for the parent backpointer + if (!Path.ROOT.equals(path)) + return factory.readElement(path, input); + return null; + } + }; + dataTreeReader = new DataTreeReader(f); + } + + /** + * Returns the appropriate reader for the given version. + */ + public ElementTreeReader getReader(int formatVersion) throws IOException { + if (formatVersion == 1) + return new ElementTreeReaderImpl_1(elementInfoFlattener); + throw new IOException(Messages.watson_unknown); + } + + /** + * Reads an element tree delta from the input stream, and + * reconstructs it as a delta on the given tree. + */ + public ElementTree readDelta(ElementTree completeTree, DataInput input) throws IOException { + /* Dispatch to the appropriate reader. */ + ElementTreeReader realReader = getReader(readNumber(input)); + return realReader.readDelta(completeTree, input); + } + + /** + * Reads a chain of ElementTrees from the given input stream. + * @return A chain of ElementTrees, where the first tree in the list is + * complete, and all other trees are deltas on the previous tree in the list. + */ + public ElementTree[] readDeltaChain(DataInput input) throws IOException { + return readDeltaChain(input, ""); //$NON-NLS-1$ + } + + /** + * Reads a chain of ElementTrees from the given input stream. + * @param input the input stream to read from. + * @param newProjectName a new name to use for the root node of the + * tree being read, or the empty String ("") to read the tree + * from the given input unchanged. + * @return A chain of ElementTrees, where the first tree in the list is + * complete, and all other trees are deltas on the previous tree in the list. + s */ + public ElementTree[] readDeltaChain(DataInput input, String newProjectName) throws IOException { + /* Dispatch to the appropriate reader. */ + ElementTreeReader realReader = getReader(readNumber(input)); + return realReader.readDeltaChain(input, newProjectName); + } + + /** + * 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 static int readNumber(DataInput input) 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 an element tree from the input stream and returns it. + * This method actually just dispatches to the appropriate reader + * depending on the stream version id. + */ + public ElementTree readTree(DataInput input) throws IOException { + return readTree(input, ""); //$NON-NLS-1$ + } + + /** + * Reads an element tree from the input stream and returns it. + * This method actually just dispatches to the appropriate reader + * depending on the stream version id. + * @param input the input stream to read from. + * @param newProjectName a new name to use for the root node of the + * tree being read, or the empty String ("") to read the tree + * from the given input unchanged. + * @return the requested ElementTree. + */ + public ElementTree readTree(DataInput input, String newProjectName) throws IOException { + /* Dispatch to the appropriate reader. */ + ElementTreeReader realReader = getReader(readNumber(input)); + return realReader.readTree(input, newProjectName); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java new file mode 100644 index 0000000000..62fef6e0fc --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * 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) - [305718] Allow reading snapshot into renamed project + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +import java.io.DataInput; +import java.io.IOException; +import org.eclipse.core.internal.dtree.DeltaDataTree; + +/** ElementTreeReader_1 is an implementation + * of the ElementTreeReader for format version 1. + * + *

Instances of this reader read only format 1 + * of a saved element tree (they do not deal with + * compatibility issues). + * + * @see ElementTreeReader + */ +/* package */class ElementTreeReaderImpl_1 extends ElementTreeReader { + + /** + * Constructs a new element tree reader that works for + * the given element info factory. + */ + ElementTreeReaderImpl_1(IElementInfoFlattener factory) { + super(factory); + } + + /** + * Reads an element tree delta from the input stream, and + * reconstructs it as a delta on the given tree. + */ + @Override + public ElementTree readDelta(ElementTree parentTree, DataInput input) throws IOException { + DeltaDataTree complete = parentTree.getDataTree(); + DeltaDataTree delta = dataTreeReader.readTree(complete, input, ""); //$NON-NLS-1$ + + //if the delta is empty, just return the parent + if (delta.isEmptyDelta()) + return parentTree; + + ElementTree tree = new ElementTree(delta); + + //copy the user data forward + IElementTreeData data = parentTree.getTreeData(); + if (data != null) { + tree.setTreeData((IElementTreeData) data.clone()); + } + + //make the underlying data tree immutable + //can't call immutable() on the ElementTree because + //this would attempt to reroot. + delta.immutable(); + return tree; + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.internal.watson.ElementTreeReader#readDeltaChain(java.io.DataInput, java.lang.String) + */ + @Override + public ElementTree[] readDeltaChain(DataInput input, String newProjectName) throws IOException { + /* read the number of trees */ + int treeCount = readNumber(input); + ElementTree[] results = new ElementTree[treeCount]; + + if (treeCount <= 0) { + return results; + } + + /* read the sort order */ + int[] order = new int[treeCount]; + for (int i = 0; i < treeCount; i++) { + order[i] = readNumber(input); + } + + /* read the complete tree */ + results[order[0]] = super.readTree(input, newProjectName); + + /* reconstitute each of the remaining trees from their written deltas */ + for (int i = 1; i < treeCount; i++) { + results[order[i]] = super.readDelta(results[order[i - 1]], input); + } + + return results; + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.internal.watson.ElementTreeReader#readTree(java.io.DataInput, java.lang.String) + */ + @Override + public ElementTree readTree(DataInput input, String newProjectName) throws IOException { + + /* The format version number has already been consumed + * by ElementTreeReader#readFrom. + */ + ElementTree result = new ElementTree(dataTreeReader.readTree(null, input, newProjectName)); + return result; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java new file mode 100644 index 0000000000..e7a2b3e569 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java @@ -0,0 +1,240 @@ +/******************************************************************************* + * 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.watson; + +import java.io.*; +import java.util.*; +import org.eclipse.core.internal.dtree.*; +import org.eclipse.core.runtime.*; + +/** ElementTreeWriter flattens an ElementTree + * onto a data output stream. + * + *

This writer generates the most up-to-date format + * of a saved element tree (cf. readers, which must usually also + * deal with backward compatibility issues). The flattened + * representation always includes a format version number. + * + *

The writer has an IElementInfoFactory, + * which it consults for writing element infos. + * + *

Element tree writers are thread-safe; several + * threads may share a single writer. + * + */ +public class ElementTreeWriter { + /** + * The current format version number. + */ + public static final int CURRENT_FORMAT = 1; + + /** + * Constant representing infinite depth + */ + public static final int D_INFINITE = DataTreeWriter.D_INFINITE; + + /** + * For writing DeltaDataTrees + */ + protected DataTreeWriter dataTreeWriter; + + /** + * Constructs a new element tree writer that works for + * the given element info flattener. + */ + public ElementTreeWriter(final IElementInfoFlattener flattener) { + + /* wrap the IElementInfoFlattener in an IDataFlattener */ + IDataFlattener f = new IDataFlattener() { + @Override + public void writeData(IPath path, Object data, DataOutput output) throws IOException { + // never write the root node of an ElementTree + //because it contains the parent backpointer. + if (!Path.ROOT.equals(path)) { + flattener.writeElement(path, data, output); + } + } + + @Override + public Object readData(IPath path, DataInput input) { + return null; + } + }; + dataTreeWriter = new DataTreeWriter(f); + } + + /** + * Sorts the given array of trees so that the following rules are true: + * - The first tree has no parent + * - No tree has an ancestor with a greater index in the array. + * If there are no missing parents in the given trees array, this means + * that in the resulting array, the i'th tree's parent will be tree i-1. + * The input tree array may contain duplicate trees. + * The sort order is written to the given output stream. + */ + protected ElementTree[] sortTrees(ElementTree[] trees, DataOutput output) throws IOException { + + /* the sorted list */ + int numTrees = trees.length; + ElementTree[] sorted = new ElementTree[numTrees]; + int[] order = new int[numTrees]; + + /* first build a table of ElementTree -> Vector of Integers(indices in trees array) */ + HashMap> table = new HashMap>(numTrees * 2 + 1); + for (int i = 0; i < trees.length; i++) { + List indices = table.get(trees[i]); + if (indices == null) { + indices = new ArrayList(); + table.put(trees[i], indices); + } + indices.add(new Integer(i)); + } + + /* find the oldest tree (a descendent of all other trees) */ + ElementTree oldest = trees[ElementTree.findOldest(trees)]; + + /** + * Walk through the chain of trees from oldest to newest, + * adding them to the sorted list as we go. + */ + int i = numTrees - 1; + while (i >= 0) { + /* add all instances of the current oldest tree to the sorted list */ + List indices = table.remove(oldest); + for (Enumeration e = Collections.enumeration(indices); e.hasMoreElements();) { + Integer next = e.nextElement(); + sorted[i] = oldest; + order[i] = next.intValue(); + i--; + } + if (i >= 0) { + /* find the next tree in the list */ + ElementTree parent = oldest.getParent(); + while (table.get(parent) == null) { + parent = parent.getParent(); + } + oldest = parent; + } + } + + /* write the order array */ + for (i = 0; i < numTrees; i++) { + writeNumber(order[i], output); + } + return sorted; + } + + /** + * Writes the delta describing the changes that have to be made + * to newerTree to obtain olderTree. + * + * @param path The path of the subtree to write. All nodes on the path above + * the subtree are represented as empty nodes. + * @param depth The depth of the subtree to write. A depth of zero writes a + * single node, and a depth of D_INFINITE writes the whole subtree. + * @param output The stream to write the subtree to. + */ + public void writeDelta(ElementTree olderTree, ElementTree newerTree, IPath path, int depth, final DataOutput output, IElementComparator comparator) throws IOException { + + /* write the version number */ + writeNumber(CURRENT_FORMAT, output); + + /** + * Note that in current ElementTree usage, the newest + * tree is the complete tree, and older trees are just + * deltas on the new tree. + */ + DeltaDataTree completeTree = newerTree.getDataTree(); + DeltaDataTree derivedTree = olderTree.getDataTree(); + DeltaDataTree deltaToWrite = null; + + deltaToWrite = completeTree.forwardDeltaWith(derivedTree, comparator); + + Assert.isTrue(deltaToWrite.isImmutable()); + dataTreeWriter.writeTree(deltaToWrite, path, depth, output); + } + + /** + * Writes an array of ElementTrees to the given output stream. + * @param trees A chain of ElementTrees, where on tree in the list is + * complete, and all other trees are deltas on the previous tree in the list. + * @param path The path of the subtree to write. All nodes on the path above + * the subtree are represented as empty nodes. + * @param depth The depth of the subtree to write. A depth of zero writes a + * single node, and a depth of D_INFINITE writes the whole subtree. + * @param output The stream to write the subtree to. + + */ + public void writeDeltaChain(ElementTree[] trees, IPath path, int depth, DataOutput output, IElementComparator comparator) throws IOException { + /* Write the format version number */ + writeNumber(CURRENT_FORMAT, output); + + /* Write the number of trees */ + int treeCount = trees.length; + writeNumber(treeCount, output); + + if (treeCount <= 0) { + return; + } + + /** + * Sort the trees in ancestral order, + * which writes the tree order to the output + */ + ElementTree[] sortedTrees = sortTrees(trees, output); + + /* Write the complete tree */ + writeTree(sortedTrees[0], path, depth, output); + + /* Write the deltas for each of the remaining trees */ + for (int i = 1; i < treeCount; i++) { + writeDelta(sortedTrees[i], sortedTrees[i - 1], path, depth, output, comparator); + } + } + + /** + * 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, DataOutput output) throws IOException { + if (number >= 0 && number < 0xff) { + output.writeByte(number); + } else { + output.writeByte(0xff); + output.writeInt(number); + } + } + + /** + * Writes all or some of an element tree to an output stream. + * This always writes the most current version of the element tree + * file format, whereas the reader supports multiple versions. + * + * @param tree The tree to write + * @param path The path of the subtree to write. All nodes on the path above + * the subtree are represented as empty nodes. + * @param depth The depth of the subtree to write. A depth of zero writes a + * single node, and a depth of D_INFINITE writes the whole subtree. + * @param output The stream to write the subtree to. + */ + public void writeTree(ElementTree tree, IPath path, int depth, final DataOutput output) throws IOException { + + /* Write the format version number. */ + writeNumber(CURRENT_FORMAT, output); + + /* This actually just copies the root node, which is what we want */ + DeltaDataTree subtree = new DeltaDataTree(tree.getDataTree().copyCompleteSubtree(Path.ROOT)); + + dataTreeWriter.writeTree(subtree, path, depth, output); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java new file mode 100644 index 0000000000..b3bef46bd0 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * 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.watson; + +import org.eclipse.core.internal.dtree.IComparator; + +/** + * This interface allows clients of the element tree to specify + * how element infos are compared, and thus how element tree deltas + * are created. + */ +public interface IElementComparator extends IComparator { + /** + * The kinds of changes + */ + public int K_NO_CHANGE = 0; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.java new file mode 100644 index 0000000000..4b14f2f53a --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.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.watson; + +/** + * An interface for objects which can visit an element of + * an element tree and access that element's node info. + * @see ElementTreeIterator + */ +public interface IElementContentVisitor { + /** Visits a node (element). + *

Note that elementContents is equal totree. + * getElement(elementPath) but takes no time. + * @param tree the element tree being visited + * @param elementContents the object at the node being visited on this call + * @param requestor callback object for requesting the path of the object being + * visited. + * @return true if this element's children should be visited, and false + * otherwise. + */ + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java new file mode 100644 index 0000000000..9646509937 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.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.watson; + +import java.io.*; +import org.eclipse.core.runtime.IPath; + +/** + * The IElementInfoFlattener interface supports + * reading and writing element info objects. + */ +public interface IElementInfoFlattener { + /** + * Reads an element info from the given input stream. + * @param elementPath 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 elementPath, + * which may be null. + */ + public Object readElement(IPath elementPath, DataInput input) throws IOException; + + /** + * Writes the given element to the output stream. + *

N.B. The bytes written must be sufficient for the + * purposes of reading the object back in. + * @param elementPath the element's path in the tree + * @param element the object associated with the given path, + * which may be null. + */ + public void writeElement(IPath elementPath, Object element, DataOutput output) throws IOException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java new file mode 100644 index 0000000000..299f95661c --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java @@ -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 + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +/** + * User data that can be attached to the element tree itself. + */ +public interface IElementTreeData extends Cloneable { + /** + * ElementTreeData must define a publicly accessible clone method. + * This method can simply invoke Object's clone method. + */ + public Object clone(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.java new file mode 100644 index 0000000000..0172182241 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.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.watson; + +import org.eclipse.core.runtime.IPath; + +/** + * Callback interface so visitors can request the path of the object they + * are visiting. This avoids creating paths when they are not needed. + */ +public interface IPathRequestor { + public IPath requestPath(); + + public String requestName(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java new file mode 100644 index 0000000000..51a5dab80a --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2009, 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; + +/** + * A description of a file info matcher. + * @since 3.6 + */ +public final class FileInfoMatcherDescription { + + private String id; + + private Object arguments; + + public FileInfoMatcherDescription(String id, Object arguments) { + super(); + this.id = id; + this.arguments = arguments; + } + + public Object getArguments() { + return arguments; + } + + public String getId() { + return id; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((arguments == null) ? 0 : arguments.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + /* (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; + FileInfoMatcherDescription other = (FileInfoMatcherDescription) obj; + if (arguments == null) { + if (other.arguments != null) + return false; + } else if (!arguments.equals(other.arguments)) + return false; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + 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/resources/IBuildConfiguration.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildConfiguration.java new file mode 100644 index 0000000000..fceba3d5b0 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildConfiguration.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 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.resources; + +import org.eclipse.core.runtime.IAdaptable; + +/** + * Build Configurations provide a mechanism for orthogonal configuration specific + * builds within a single project. The resources plugin maintains build deltas per + * interested builder, per configuration, and allow build configurations to reference + * each other. + *

+ * All projects have at least one build configuration. By default this + * has name {@link #DEFAULT_CONFIG_NAME}. One configuration in the project is defined + * to be 'active'. The active configuration is built by default. If unset, the + * active configuration defaults to the first configuration in the project. + *

+ *

+ * Build configurations are created and set on the project description using: + * {@link IProjectDescription#setBuildConfigs(String[])}. + * Build configurations set on Projects must have unique non-null names. + *

+ *

+ * When a project is built, a specific configuration is built. This configuration + * is passed to the builders so they can adapt their behavior + * appropriately. Builders which don't care about configurations may ignore this, + * and work as before. + *

+ *

+ * Build configuration can reference other builds configurations. These references are created + * using {@link IWorkspace#newBuildConfig(String, String)}, and set on the referencing project + * with {@link IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[])}. + * A referenced build configuration may have a null configuration name which is resolved to the + * referenced project's current active build configuration at build time. + *

+ *

+ * Workspace build will ensure that the projects are built in an appropriate order as defined + * by the reference graph. + *

+ * + * @see IWorkspace#newBuildConfig(String, String) + * @see IProjectDescription#setActiveBuildConfig(String) + * @see IProjectDescription#setBuildConfigs(String[]) + * @see IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[]) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * @since 3.7 + */ +public interface IBuildConfiguration extends IAdaptable { + + /** + * The Id of the default build configuration + */ + public static final String DEFAULT_CONFIG_NAME = ""; //$NON-NLS-1$ + + /** + * @return the project that the config is for; never null. + */ + public IProject getProject(); + + /** + * Returns the human readable name of this build configuration. If this + * {@link IBuildConfiguration} is set on a Project, this can never be null. + *

+ * If this IBuildConfiguration is being used as a reference to a build configuration + * in another project, this may be null. Such build configuration references are + * resolved to the current active configuration at build time. + *

+ * @return the name of the configuration; or null if this is a reference to the active + * configuration + */ + public String getName(); + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java new file mode 100644 index 0000000000..b126ebf81b --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 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.resources; + +/** + * Stores information about the context in which a builder was called. + * + *

+ * This can be interrogated by a builder to determine what's been built + * before, and what's being built after it, for this particular build + * invocation. + *

+ * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * + * @since 3.7 + */ +public interface IBuildContext { + + /** + * Gets a array of build configurations that were built before this build configuration, + * as part of the current top-level build invocation. + * + * @return an array of all referenced build configurations that have been built + * in the current build; never null. + */ + public IBuildConfiguration[] getAllReferencedBuildConfigs(); + + /** + * Gets a array of build configurations that will be built after this build configuration, + * as part of the current top-level build invocation. + *

+ * If the array is empty, this configuration is the last in the build chain. + *

+ * + * @return an array of all referencing build configurations that will be built + * in the current build; never null. + */ + public IBuildConfiguration[] getAllReferencingBuildConfigs(); + + /** + * Returns the full array of configurations that were requested to be built + * by the API user. These configurations may be anywhere in the build + * order (depending on how the build graph has been flattened). + *

+ * This array won't include any build configurations being built by virtue + * of being referenced from a requested build configuration. + *

+ * May return the empty array if this is a top-level workspace build. + * + * @return an array of configurations that were requested to be built. + */ + public IBuildConfiguration[] getRequestedConfigs(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java new file mode 100644 index 0000000000..17f29e4d36 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Map; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A builder command names a builder and supplies a table of + * name-value argument pairs. + *

+ * Changes to a command will only take effect if the modified command is installed + * into a project description via {@link IProjectDescription#setBuildSpec(ICommand[])}. + *

+ * + * @see IProjectDescription + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ICommand { + + /** + * Returns a table of the arguments for this command, or null + * if there are no arguments. The argument names and values are both strings. + * + * @return a table of command arguments (key type : String + * value type : String), or null + * @see #setArguments(Map) + */ + public Map getArguments(); + + /** + * Returns the name of the builder to run for this command, or + * null if the name has not been set. + * + * @return the name of the builder, or null if not set + * @see #setBuilderName(String) + */ + public String getBuilderName(); + + /** + * Returns whether this build command responds to the given kind of build. + *

+ * By default, build commands respond to all kinds of builds. + *

+ * + * @param kind One of the *_BUILD constants defined + * on IncrementalProjectBuilder + * @return true if this build command responds to the specified + * kind of build, and false otherwise. + * @see #setBuilding(int, boolean) + * @since 3.1 + */ + public boolean isBuilding(int kind); + + /** + * Returns whether this command allows configuring of what kinds of builds + * it responds to. By default, commands are only configurable + * if the corresponding builder defines the {@link #isConfigurable} + * attribute in its builder extension declaration. A command that is not + * configurable will always respond to all kinds of builds. + * + * @return true If this command allows configuration of + * what kinds of builds it responds to, and false otherwise. + * @see #setBuilding(int, boolean) + * @since 3.1 + */ + public boolean isConfigurable(); + + /** + * Sets this command's arguments to be the given table of name-values + * pairs, or to null if there are no arguments. The argument + * names and values are both strings. + *

+ * Individual builders specify their argument expectations. + *

+ *

+ * Note that modifications to the arguments of a command + * being used in a running builder may affect the run of that builder + * but will not affect any subsequent runs. To change a command + * permanently you must install the command into the relevant project + * build spec using {@link IProjectDescription#setBuildSpec(ICommand[])}. + *

+ * + * @param args a table of command arguments (keys and values must + * both be of type String), or null + * @see #getArguments() + */ + public void setArguments(Map args); + + /** + * Sets the name of the builder to run for this command. + *

+ * The builder name comes from the extension that plugs in + * to the standard org.eclipse.core.resources.builders + * extension point. + *

+ * + * @param builderName the name of the builder + * @see #getBuilderName() + */ + public void setBuilderName(String builderName); + + /** + * Specifies whether this build command responds to the provided kind of build. + *

+ * When a command is configured to not respond to a given kind of build, the + * builder instance will not be called when a build of that kind is initiated. + *

+ * This method has no effect if this build command does not allow its + * build kinds to be configured. + *

+ * Note: + *

    + *
  • A request for INCREMENTAL_BUILD or AUTO_BUILD will result in the builder being called with the FULL_BUILD + * kind, if there is no previous delta (e.g. after a clean build). + *
  • + * If INCREMENTAL_BUILD (or AUTO_BUILD) is promoted to FULL_BUILD, the builder will be called, + * if the command responds to INCREMENTAL_BUILD (or AUTO_BUILD). + *
  • + * If INCREMENTAL_BUILD is promoted to FULL_BUILD, the builder will be called, + * if the command responds to FULL_BUILD. + *
  • + * If AUTO_BUILD is promoted to FULL_BUILD, the builder will be called, + * only if the command responds to AUTO_BUILD. + *
  • + *
+ * + * @param kind One of the *_BUILD constants defined + * on IncrementalProjectBuilder + * @param value true if this build command responds to the + * specified kind of build, and false otherwise. + * @see #isBuilding(int) + * @see #isConfigurable() + * @see IWorkspace#build(int, IProgressMonitor) + * @see IProject#build(int, IProgressMonitor) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#AUTO_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @since 3.1 + */ + public void setBuilding(int kind, boolean value); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java new file mode 100644 index 0000000000..a84b370ae3 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java @@ -0,0 +1,562 @@ +/******************************************************************************* + * 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 + * Serge Beauchamp (Freescale Semiconductor) - [252996] add addFilter/removeFilter/getFilters + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.runtime.*; + +/** + * Interface for resources which may contain + * other resources (termed its members). While the + * workspace itself is not considered a container in this sense, the + * workspace root resource is a container. + *

+ * Containers implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

+ * + * @see Platform#getAdapterManager() + * @see IProject + * @see IFolder + * @see IWorkspaceRoot + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IContainer extends IResource, IAdaptable { + + /*==================================================================== + * Constants defining which members are wanted: + *====================================================================*/ + + /** + * Member constant (bit mask value 1) indicating that phantom member resources are + * to be included. + * + * @see IResource#isPhantom() + * @since 2.0 + */ + public static final int INCLUDE_PHANTOMS = 1; + + /** + * Member constant (bit mask value 2) indicating that team private members are + * to be included. + * + * @see IResource#isTeamPrivateMember() + * @since 2.0 + */ + public static final int INCLUDE_TEAM_PRIVATE_MEMBERS = 2; + + /** + * Member constant (bit mask value 4) indicating that derived resources + * are to be excluded. + * + * @see IResource#isDerived() + * @since 3.1 + */ + public static final int EXCLUDE_DERIVED = 4; + + /** + * Member constant (bit mask value 8) indicating that hidden resources + * are to be included. + * + * @see IResource#isHidden() + * @since 3.4 + */ + public static final int INCLUDE_HIDDEN = 8; + + /** + * Member constant (bit mask value 16) indicating that a resource + * should not be checked for existence. + * + * @see IResource#accept(IResourceProxyVisitor, int) + * @see IResource#accept(IResourceVisitor, int, int) + * @since 3.8 + */ + public static final int DO_NOT_CHECK_EXISTENCE = 16; + + /** + * Returns whether a resource of some type with the given path + * exists relative to this resource. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators are ignored. + * If the path is empty this container is checked for existence. + * + * @param path the path of the resource + * @return true if a resource of some type with the given path + * exists relative to this resource, and false otherwise + * @see IResource#exists() + */ + public boolean exists(IPath path); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

+ * Note that no attempt is made to exclude team-private member resources + * as with members. + *

+ * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource existing at the calculated path in the workspace. + *

+ * Note that path contains a relative path to the resource + * and all path special characters will be interpreted. Passing an empty string + * will result in returning this {@link IContainer} itself. + *

+ * + * @param path the relative path to the member resource, must be a valid path or path segment + * @return the member resource, or null if no such + * resource exists + * @see #members() + * @see IPath#isValidPath(String) + * @see IPath#isValidSegment(String) + */ + public IResource findMember(String path); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

+ * If the includePhantoms argument is false, + * only a member resource with the given path that exists will be returned. + * If the includePhantoms argument is true, + * the method also returns a resource if the workspace is keeping track of a + * phantom with that path. + *

+ * Note that no attempt is made to exclude team-private member resources + * as with members. + *

+ * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource (or phantom) existing at the calculated path in the workspace. + *

+ * Note that path contains a relative path to the resource + * and all path special characters will be interpreted. Passing an empty string + * will result in returning this {@link IContainer} itself. + *

+ * + * @param path the relative path to the member resource, must be a valid path or path segment + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @return the member resource, or null if no such + * resource exists + * @see #members(boolean) + * @see IResource#isPhantom() + * @see IPath#isValidPath(String) + * @see IPath#isValidSegment(String) + */ + public IResource findMember(String path, boolean includePhantoms); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

+ * Note that no attempt is made to exclude team-private member resources + * as with members. + *

+ * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource existing at the calculated path in the workspace. + *

+ * + * @param path the path of the desired resource + * @return the member resource, or null if no such + * resource exists + */ + public IResource findMember(IPath path); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

+ * If the includePhantoms argument is false, + * only a member resource with the given path that exists will be returned. + * If the includePhantoms argument is true, + * the method also returns a resource if the workspace is keeping track of a + * phantom with that path. + *

+ * Note that no attempt is made to exclude team-private member resources + * as with members. + *

+ * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource (or phantom) existing at the calculated path in the workspace. + *

+ * + * @param path the path of the desired resource + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @return the member resource, or null if no such + * resource exists + * @see #members(boolean) + * @see IResource#isPhantom() + */ + public IResource findMember(IPath path, boolean includePhantoms); + + /** + * Returns the default charset for resources in this container. + *

+ * This is a convenience method, fully equivalent to: + *

+	 *   getDefaultCharset(true);
+	 * 
+ *

+ * Note that this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

+ * + * @return the name of the default charset encoding for this container + * @exception CoreException if this method fails + * @see IContainer#getDefaultCharset(boolean) + * @see IFile#getCharset() + * @since 3.0 + */ + public String getDefaultCharset() throws CoreException; + + /** + * Returns the default charset for resources in this container. + *

+ * If checkImplicit is false, this method + * will return the charset defined by calling #setDefaultCharset, provided this + * container exists, or null otherwise. + *

+ * If checkImplicit is true, this method uses the following + * algorithm to determine the charset to be returned: + *

    + *
  1. the one explicitly set by calling #setDefaultCharset + * (with a non-null argument) on this container, if any, and this container + * exists, or
  2. + *
  3. the parent's default charset, if this container has a parent (is not the + * workspace root), or
  4. + *
  5. the charset returned by ResourcesPlugin#getEncoding.
  6. + *
+ *

+ * Note that this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

+ * @return the name of the default charset encoding for this container, + * or null + * @exception CoreException if this method fails + * @see IFile#getCharset() + * @since 3.0 + */ + public String getDefaultCharset(boolean checkImplicit) throws CoreException; + + /** + * Returns a handle to the file identified by the given path in this + * container. + *

+ * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

+ * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource and is appended + * to this container's full path to form the full path of the resultant resource. + * A trailing separator is ignored. The path of the resulting resource must + * have at least two segments. + *

+ * + * @param path the path of the member file + * @return the (handle of the) member file + * @see #getFolder(IPath) + */ + public IFile getFile(IPath path); + + /** + * Returns a handle to the folder identified by the given path in this + * container. + *

+ * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

+ * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource and is appended + * to this container's full path to form the full path of the resultant resource. + * A trailing separator is ignored. The path of the resulting resource must + * have at least two segments. + *

+ * + * @param path the path of the member folder + * @return the (handle of the) member folder + * @see #getFile(IPath) + */ + public IFolder getFolder(IPath path); + + /** + * Returns a list of existing member resources (projects, folders and files) + * in this resource, in no particular order. + *

+ * This is a convenience method, fully equivalent to members(IResource.NONE). + * Team-private member resources are not included in the result. + *

+ * Note that the members of a project or folder are the files and folders + * immediately contained within it. The members of the workspace root + * are the projects in the workspace. + *

+ * + * @return an array of members of this resource + * @exception CoreException if this request fails. Reasons include: + *
    + *
  • This resource does not exist.
  • + *
  • This resource is a project that is not open.
  • + *
+ * @see #findMember(IPath) + * @see IResource#isAccessible() + */ + public IResource[] members() throws CoreException; + + /** + * Returns a list of all member resources (projects, folders and files) + * in this resource, in no particular order. + *

+ * This is a convenience method, fully equivalent to: + *

+	 *   members(includePhantoms ? INCLUDE_PHANTOMS : IResource.NONE);
+	 * 
+ * Team-private member resources are not included in the result. + *

+ * + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @return an array of members of this resource + * @exception CoreException if this request fails. Reasons include: + *
    + *
  • This resource does not exist.
  • + *
  • includePhantoms is false and + * this resource does not exist.
  • + *
  • includePhantoms is false and + * this resource is a project that is not open.
  • + *
+ * @see #members(int) + * @see IResource#exists() + * @see IResource#isPhantom() + */ + public IResource[] members(boolean includePhantoms) throws CoreException; + + /** + * Returns a list of all member resources (projects, folders and files) + * in this resource, in no particular order. + *

+ * If the INCLUDE_PHANTOMS flag is not specified in the member + * flags (recommended), only member resources that exist will be returned. + * If the INCLUDE_PHANTOMS flag is specified, + * the result will also include any phantom member resources the + * workspace is keeping track of. + *

+ * If the INCLUDE_TEAM_PRIVATE_MEMBERS flag is specified + * in the member flags, team private members will be included along with + * the others. If the INCLUDE_TEAM_PRIVATE_MEMBERS flag + * is not specified (recommended), the result will omit any team private + * member resources. + *

+ * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, hidden + * members will be included along with the others. If the {@link #INCLUDE_HIDDEN} flag + * is not specified (recommended), the result will omit any hidden + * member resources. + *

+ *

+ * If the EXCLUDE_DERIVED flag is not specified, derived + * resources are included. If the EXCLUDE_DERIVED flag is + * specified in the member flags, derived resources are not included. + *

+ * + * @param memberFlags bit-wise or of member flag constants + * ({@link #INCLUDE_PHANTOMS}, {@link #INCLUDE_TEAM_PRIVATE_MEMBERS}, + * {@link #INCLUDE_HIDDEN} and {@link #EXCLUDE_DERIVED}) indicating which members are of interest + * @return an array of members of this resource + * @exception CoreException if this request fails. Reasons include: + *
    + *
  • This resource does not exist.
  • + *
  • the INCLUDE_PHANTOMS flag is not specified and + * this resource does not exist.
  • + *
  • the INCLUDE_PHANTOMS flag is not specified and + * this resource is a project that is not open.
  • + *
+ * @see IResource#exists() + * @since 2.0 + */ + public IResource[] members(int memberFlags) throws CoreException; + + /** + * Returns a list of recently deleted files inside this container that + * have one or more saved states in the local history. The depth parameter + * determines how deep inside the container to look. This resource may or + * may not exist in the workspace. + *

+ * When applied to an existing project resource, this method returns recently + * deleted files with saved states in that project. Note that local history is + * maintained with each individual project, and gets discarded when a project + * is deleted from the workspace. If applied to a deleted project, this method + * returns the empty list. + *

+ * When applied to the workspace root resource (depth infinity), this method + * returns all recently deleted files with saved states in all existing projects. + *

+ * When applied to a folder (or project) resource (depth one), + * this method returns all recently deleted member files with saved states. + *

+ * When applied to a folder resource (depth zero), + * this method returns an empty list unless there was a recently deleted file + * with saved states at the same path as the folder. + *

+ * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

+ * + * @param depth depth limit: one of DEPTH_ZERO, DEPTH_ONE + * or DEPTH_INFINITE + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @return an array of recently deleted files + * @exception CoreException if this method fails + * @see IFile#getHistory(IProgressMonitor) + * @since 2.0 + */ + public IFile[] findDeletedMembersWithHistory(int depth, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the default charset for this container. Passing a value of null + * will remove the default charset setting for this resource. + * + * @param charset a charset string, or null + * @exception CoreException if this method fails Reasons include: + *
    + *
  • This resource does not exist.
  • + *
  • An error happened while persisting this setting.
  • + *
+ * @see IContainer#getDefaultCharset() + * @since 3.0 + * @deprecated Replaced by {@link #setDefaultCharset(String, IProgressMonitor)} which + * is a workspace operation and reports changes in resource deltas. + */ + @Deprecated + public void setDefaultCharset(String charset) throws CoreException; + + /** + * Sets the default charset for this container. Passing a value of null + * will remove the default charset setting for this resource. + *

+ * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the encoding of affected resources has been changed. + *

+ *

+ * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

+ * + * @param charset a charset string, or null + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @exception CoreException if this method fails Reasons include: + *
    + *
  • This resource is not accessible.
  • + *
  • An error happened while persisting this setting.
  • + *
  • Resource changes are disallowed during certain types of resource change + * event notification. See {@link IResourceChangeEvent} for more details.
  • + *
+ * @see IContainer#getDefaultCharset() + * @see IResourceRuleFactory#charsetRule(IResource) + * @since 3.0 + */ + public void setDefaultCharset(String charset, IProgressMonitor monitor) throws CoreException; + + /** + * Adds a new filter to this container. Filters restrict the set of files and directories + * in the underlying file system that will be included as members of this container. + *

+ * The {@link IResource#BACKGROUND_REFRESH} update flag controls when + * changes to the resource hierarchy under this container resulting from the new + * filter take effect. If this flag is specified, the resource hierarchy is updated in a + * separate thread after this method returns. If the flag is not specified, any resource + * changes resulting from the new filter will occur before this method returns. + *

+ *

+ * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include an indication of any + * resources that have been removed as a result of the new filter. + *

+ *

+ * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

+ * + * @param type ({@link IResourceFilterDescription#INCLUDE_ONLY} or + * {@link IResourceFilterDescription#EXCLUDE_ALL} and/or {@link IResourceFilterDescription#INHERITABLE}) + * @param matcherDescription the description of the matcher that will determine + * which {@link IFileInfo} instances will be excluded from the resource tree + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#BACKGROUND_REFRESH}) + * @param monitor a progress monitor, or null if progress reporting is not desired + * @return the description of the added filter + * @exception CoreException if this filter could not be added. Reasons include: + *
    + *
  • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
  • + *
+ * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #getFilters() + * @see IResourceFilterDescription#delete(int, IProgressMonitor) + * + * @since 3.6 + */ + public IResourceFilterDescription createFilter(int type, FileInfoMatcherDescription matcherDescription, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Retrieve all filters on this container. + * If no filters exist for this resource, an empty array is returned. + * + * @return an array of filters + * @exception CoreException if this resource's filters could not be retrieved. Reasons include: + *
    + *
  • This resource is not a folder.
  • + * + * @see #createFilter(int, FileInfoMatcherDescription, int, IProgressMonitor) + * @see IResourceFilterDescription#delete(int, IProgressMonitor) + * @since 3.6 + */ + public IResourceFilterDescription[] getFilters() throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java new file mode 100644 index 0000000000..953f021a8e --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * A storage that knows how its contents are encoded. + * + *

    The IEncodedStorage interface extends IStorage + * in order to provide access to the charset to be used when decoding its + * contents. + *

    + * Clients may implement this interface. + *

    + * + * @since 3.0 + */ +public interface IEncodedStorage extends IStorage { + /** + * Returns the name of a charset encoding to be used when decoding this + * storage's contents into characters. Returns null if a proper + * encoding cannot be determined. + *

    + * Note that this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

    + * + * @return the name of a charset, or null + * @exception CoreException if an error happens while determining + * the charset. See any refinements for more information. + * @see IStorage#getContents() + */ + public String getCharset() throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java new file mode 100644 index 0000000000..8bbd71b2a5 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java @@ -0,0 +1,1146 @@ +/******************************************************************************* + * 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.resources; + +import java.io.InputStream; +import java.io.Reader; +import java.net.URI; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentDescription; +import org.eclipse.core.runtime.content.IContentTypeManager; + +/** + * Files are leaf resources which contain data. + * The contents of a file resource is stored as a file in the local + * file system. + *

    + * Files, like folders, may exist in the workspace but + * not be local; non-local file resources serve as place-holders for + * files whose content and properties have not yet been fetched from + * a repository. + *

    + *

    + * Files implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

    + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFile extends IResource, IEncodedStorage, IAdaptable { + /** + * Character encoding constant (value 0) which identifies + * files that have an unknown character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UNKNOWN = 0; + /** + * Character encoding constant (value 1) which identifies + * files that are encoded with the US-ASCII character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_US_ASCII = 1; + /** + * Character encoding constant (value 2) which identifies + * files that are encoded with the ISO-8859-1 character encoding scheme, + * also known as ISO-LATIN-1. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_ISO_8859_1 = 2; + /** + * Character encoding constant (value 3) which identifies + * files that are encoded with the UTF-8 character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_8 = 3; + /** + * Character encoding constant (value 4) which identifies + * files that are encoded with the UTF-16BE character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_16BE = 4; + /** + * Character encoding constant (value 5) which identifies + * files that are encoded with the UTF-16LE character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_16LE = 5; + /** + * Character encoding constant (value 6) which identifies + * files that are encoded with the UTF-16 character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_16 = 6; + + /** + * Appends the entire contents of the given stream to this file. + *

    + * This is a convenience method, fully equivalent to: + *

    +	 *   appendContents(source, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
    +	 * 
    + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

    + *

    + * This method is long-running; progress and cancelation are provided + * by the given progress monitor. + *

    + * + * @param source an input stream containing the new contents of the file + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param keepHistory a flag indicating whether or not to store + * the current contents in the local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • The corresponding location in the local file system + * is occupied by a directory.
    • + *
    • The workspace is not in sync with the corresponding location + * in the local file system and force is false.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    • The file modification validator disallowed the change.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #appendContents(java.io.InputStream,int,IProgressMonitor) + */ + public void appendContents(InputStream source, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Appends the entire contents of the given stream to this file. + * The stream, which must not be null, will get closed + * whether this method succeeds or fails. + *

    + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite a corresponding file in the local file system provided + * it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write a corresponding file in the local file system, overwriting any + * existing one if need be. In either case, if this method succeeds, the + * resource will be marked as being local (even if it wasn't before). + *

    + *

    + * If this file is non-local then this method will always fail. The only exception + * is when FORCE is specified and the file exists in the local + * file system. In this case the file is made local and the given contents are appended. + *

    + *

    + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of this file should be captured in the workspace's local + * history (properties are not recorded in the local history). The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying KEEP_HISTORY + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. This flag is ignored if the file was not previously local. + *

    + *

    + * Update flags other than FORCE and KEEP_HISTORY + * are ignored. + *

    + *

    + * Prior to modifying the contents of this file, the file modification validator (if provided + * by the VCM plug-in), will be given a chance to perform any last minute preparations. Validation + * is performed by calling IFileModificationValidator.validateSave on this file. + * If the validation fails, then this operation will fail. + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

    + *

    + * This method is long-running; progress and cancelation are provided + * by the given progress monitor. + *

    + * + * @param source an input stream containing the new contents of the file + * @param updateFlags bit-wise or of update flag constants + * (FORCE and KEEP_HISTORY) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • The corresponding location in the local file system + * is occupied by a directory.
    • + *
    • The workspace is not in sync with the corresponding location + * in the local file system and FORCE is not specified.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    • The file modification validator disallowed the change.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void appendContents(InputStream source, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + *

    + * This is a convenience method, fully equivalent to: + *

    +	 *   create(source, (force ? FORCE : IResource.NONE), monitor);
    +	 * 
    + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param source an input stream containing the initial contents of the file, + * or null if the file should be marked as not local + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource already exists in the workspace.
    • + *
    • The parent of this resource does not exist.
    • + *
    • The parent of this resource is a virtual folder.
    • + *
    • The project of this resource is not accessible.
    • + *
    • The parent contains a resource of a different type + * at the same path as this resource.
    • + *
    • The name of this resource is not valid (according to + * IWorkspace.validateName).
    • + *
    • The corresponding location in the local file system is occupied + * by a directory.
    • + *
    • The corresponding location in the local file system is occupied + * by a file and force is false.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + */ + public void create(InputStream source, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + * The resource's contents are supplied by the data in the given stream. + * This method closes the stream whether it succeeds or fails. + * If the stream is null then a file is not created in the local + * file system and the created file resource is marked as being non-local. + *

    + * The {@link IResource#FORCE} update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If {@link IResource#FORCE} is not specified, the method will only attempt + * to write a file in the local file system if it does not already exist. + * This option ensures there is no unintended data loss; it is the recommended + * setting. However, if {@link IResource#FORCE} is specified, this method will + * attempt to write a corresponding file in the local file system, + * overwriting any existing one if need be. + *

    + *

    + * The {@link IResource#DERIVED} update flag indicates that this resource + * should immediately be set as a derived resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setDerived(boolean)} + * with a value of true immediately after creating the resource. + *

    + *

    + * The {@link IResource#TEAM_PRIVATE} update flag indicates that this resource + * should immediately be set as a team private resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setTeamPrivateMember(boolean)} + * with a value of true immediately after creating the resource. + *

    + *

    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

    + *

    + * Update flags other than those listed above are ignored. + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param source an input stream containing the initial contents of the file, + * or null if the file should be marked as not local + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#FORCE}, {@link IResource#DERIVED}, and {@link IResource#TEAM_PRIVATE}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource already exists in the workspace.
    • + *
    • The parent of this resource does not exist.
    • + *
    • The parent of this resource is a virtual folder.
    • + *
    • The project of this resource is not accessible.
    • + *
    • The parent contains a resource of a different type + * at the same path as this resource.
    • + *
    • The name of this resource is not valid (according to + * IWorkspace.validateName).
    • + *
    • The corresponding location in the local file system is occupied + * by a directory.
    • + *
    • The corresponding location in the local file system is occupied + * by a file and FORCE is not specified.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#createRule(IResource) + * @since 2.0 + */ + public void create(InputStream source, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + * The file's contents will be located in the file specified by the given + * file system path. The given path must be either an absolute file system + * path, or a relative path whose first segment is the name of a workspace path + * variable. + *

    + * The {@link IResource#ALLOW_MISSING_LOCAL} update flag controls how this + * method deals with cases where the local file system file to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If {@link IResource#ALLOW_MISSING_LOCAL} is specified, the operation will succeed + * even if the local file is missing, or the path is relative to an undefined + * variable. If {@link IResource#ALLOW_MISSING_LOCAL} is not specified, the operation + * will fail in the case where the local file system file does not exist or the + * path is relative to an undefined variable. + *

    + *

    + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then the existing linked resource's location is replaced + * by localLocation's value. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

    + *

    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

    + *

    + * Update flags other than those listed above are ignored. + *

    + *

    + * This method synchronizes this resource with the local file system at the given + * location. + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param localLocation a file system path where the file should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE} and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource already exists in the workspace.
    • + *
    • The workspace contains a resource of a different type + * at the same path as this resource.
    • + *
    • The parent of this resource does not exist.
    • + *
    • The parent of this resource is not an open project
    • + *
    • The name of this resource is not valid (according to + * IWorkspace.validateName).
    • + *
    • The corresponding location in the local file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
    • + *
    • The corresponding location in the local file system is occupied + * by a directory (as opposed to a file).
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    • The team provider for the project which contains this folder does not permit + * linked resources.
    • + *
    • This folder's project contains a nature which does not permit linked resources.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#HIDDEN + * @since 2.1 + */ + public void createLink(IPath localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + * The file's contents will be located in the file specified by the given + * URI. The given URI must be either absolute, or a relative URI whose first path + * segment is the name of a workspace path variable. + *

    + * The ALLOW_MISSING_LOCAL update flag controls how this + * method deals with cases where the file system file to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If ALLOW_MISSING_LOCAL is specified, the operation will succeed + * even if the local file is missing, or the path is relative to an undefined + * variable. If ALLOW_MISSING_LOCAL is not specified, the operation + * will fail in the case where the file system file does not exist or the + * path is relative to an undefined variable. + *

    + *

    + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then any existing resource with the same name is removed + * from the workspace to make way for creation of the link. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

    + *

    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

    + *

    + * Update flags other than those listed above are ignored. + *

    + *

    + * This method synchronizes this resource with the file system at the given + * location. + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param location a file system URI where the file should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE} and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource already exists in the workspace.
    • + *
    • The workspace contains a resource of a different type + * at the same path as this resource.
    • + *
    • The parent of this resource does not exist.
    • + *
    • The parent of this resource is not an open project
    • + *
    • The name of this resource is not valid (according to + * IWorkspace.validateName).
    • + *
    • The corresponding location in the file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
    • + *
    • The corresponding location in the file system is occupied + * by a directory (as opposed to a file).
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    • The team provider for the project which contains this folder does not permit + * linked resources.
    • + *
    • This folder's project contains a nature which does not permit linked resources.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#HIDDEN + * @since 3.2 + */ + public void createLink(URI location, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this file from the workspace. + *

    + * This is a convenience method, fully equivalent to: + *

    +	 *   delete((keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
    +	 * 
    + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this folder has been removed from its parent. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource could not be deleted for some reason.
    • + *
    • This resource is out of sync with the local file system + * and force is false.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + * @see IResourceRuleFactory#deleteRule(IResource) + */ + public void delete(boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the name of a charset to be used when decoding the contents of this + * file into characters. + *

    + * This refinement of the corresponding {@link IEncodedStorage} method + * is a convenience method, fully equivalent to: + *

    +	 *   getCharset(true);
    +	 * 
    + *

    + * Note 1: this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

    + *

    + * Note 2: this method returns a cached value for the encoding + * that may be out of date if the file is not synchronized with the local file system + * and the encoding has since changed in the file system. + *

    + * + * @return the name of a charset + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource could not be read.
    • + *
    • This resource is not local.
    • + *
    • The corresponding location in the local file system + * is occupied by a directory.
    • + *
    + * @see IFile#getCharset(boolean) + * @see IEncodedStorage#getCharset() + * @see IContainer#getDefaultCharset() + * @since 3.0 + */ + @Override + public String getCharset() throws CoreException; + + /** + * Returns the name of a charset to be used when decoding the contents of this + * file into characters. + *

    + * If checkImplicit is false, this method will return the + * charset defined by calling setCharset, provided this file + * exists, or null otherwise. + *

    + * If checkImplicit is true, this method uses the following + * algorithm to determine the charset to be returned: + *

      + *
    1. the charset defined by calling #setCharset, if any, and this file + * exists, or
    2. + *
    3. the charset automatically discovered based on this file's contents, + * if one can be determined, or
    4. + *
    5. the default encoding for this file's parent (as defined by + * IContainer#getDefaultCharset).
    6. + *
    + *

    + * Note 1: this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

    + *

    + * Note 2: this method returns a cached value for the encoding + * that may be out of date if the file is not synchronized with the local file system + * and the encoding has since changed in the file system. + *

    + * + * @return the name of a charset, or null + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource could not be read.
    • + *
    • This resource is not local.
    • + *
    • The corresponding location in the local file system + * is occupied by a directory.
    • + *
    + * @see IEncodedStorage#getCharset() + * @see IContainer#getDefaultCharset() + * @since 3.0 + */ + public String getCharset(boolean checkImplicit) throws CoreException; + + /** + * Returns the name of a charset to be used to encode the given contents + * when saving to this file. This file does not have to exist. The character stream is not automatically closed. + *

    + * This method uses the following algorithm to determine the charset to be returned: + *

      + *
    1. if this file exists, the charset returned by IFile#getCharset(false), if one is defined, or
    2. + *
    3. the charset automatically discovered based on the file name and the given contents, + * if one can be determined, or
    4. + *
    5. the default encoding for the parent resource (as defined by + * IContainer#getDefaultCharset).
    6. + *
    + *

    + * Note: this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

    + * + * @param reader a character stream containing the contents to be saved into this file + * @return the name of a charset + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • The given character stream could not be read.
    • + *
    + * @see #getCharset(boolean) + * @see IContainer#getDefaultCharset() + * @since 3.1 + */ + public String getCharsetFor(Reader reader) throws CoreException; + + /** + * Returns a description for this file's current contents. Returns + * null if a description cannot be obtained. + *

    + * Calling this method produces a similar effect as calling + * getDescriptionFor(getContents(), getName(), IContentDescription.ALL) + * on IContentTypeManager, but provides better + * opportunities for improved performance. Therefore, when manipulating + * IFiles, clients should call this method instead of + * IContentTypeManager.getDescriptionFor. + *

    + * + * @return a description for this file's current contents, or + * null + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • This resource could not be read.
    • + *
    • This resource is not local.
    • + *
    • The workspace is not in sync with the corresponding location + * in the local file system and {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is + * disabled.
    • + *
    • The corresponding location in the local file system + * is occupied by a directory.
    • + *
    + * @see IContentDescription + * @see IContentTypeManager#getDescriptionFor(InputStream, String, QualifiedName[]) + * @since 3.0 + */ + public IContentDescription getContentDescription() throws CoreException; + + /** + * Returns an open input stream on the contents of this file. + *

    + * This refinement of the corresponding {@link IStorage} method + * is a convenience method returning an open input stream. It's equivalent to: + *

    +	 *   getContents(RefreshManager#PREF_LIGHTWEIGHT_AUTO_REFRESH);
    +	 * 
    + *

    + *

    + * If lightweight auto-refresh is not enabled this method will throw a CoreException + * when opening out-of-sync resources. + *

    + * The client is responsible for closing the stream when finished. + * + * @return an input stream containing the contents of the file + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • This resource is not local.
    • + *
    • The file-system resource is not a file.
    • + *
    • The workspace is not in sync with the corresponding location + * in the local file system (and {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} + * is disabled).
    • + *
    + */ + @Override + public InputStream getContents() throws CoreException; + + /** + * This refinement of the corresponding IStorage method + * returns an open input stream on the contents of this file. + * The client is responsible for closing the stream when finished. + * If force is true the file is opened and an input + * stream returned regardless of the sync state of the file. The file + * is not synchronized with the workspace. + * If force is false the method fails if not in sync. + * + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @return an input stream containing the contents of the file + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • This resource is not local.
    • + *
    • The workspace is not in sync with the corresponding location + * in the local file system and force is false.
    • + *
    + */ + public InputStream getContents(boolean force) throws CoreException; + + /** + * Returns a constant identifying the character encoding of this file, or + * ENCODING_UNKNOWN if it could not be determined. The returned constant + * will be one of the ENCODING_* constants defined on IFile. + * + * This method attempts to guess the file's character encoding by analyzing + * the first few bytes of the file. If no identifying pattern is found at the + * beginning of the file, ENC_UNKNOWN will be returned. This method will + * not attempt any complex analysis of the file to make a guess at the + * encoding that is used. + * + * @return The character encoding of this file + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • This resource could not be read.
    • + *
    • This resource is not local.
    • + *
    • The corresponding location in the local file system + * is occupied by a directory.
    • + *
    + * @deprecated use {@link #getCharset} instead + */ + @Deprecated + public int getEncoding() throws CoreException; + + /** + * Returns the full path of this file. + * This refinement of the corresponding IStorage and IResource + * methods links the semantics of resource and storage object paths such that + * IFiles always have a path and that path is relative to the + * containing workspace. + * + * @see IResource#getFullPath() + * @see IStorage#getFullPath() + */ + @Override + public IPath getFullPath(); + + /** + * Returns a list of past states of this file known to this workspace. + * Recently added states first. + *

    + * 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 + * @return an array of states of this file + * @exception CoreException if this method fails. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + */ + public IFileState[] getHistory(IProgressMonitor monitor) throws CoreException; + + /** + * Returns the name of this file. + * This refinement of the corresponding IStorage and IResource + * methods links the semantics of resource and storage object names such that + * IFiles always have a name and that name equivalent to the + * last segment of its full path. + * + * @see IResource#getName() + * @see IStorage#getName() + */ + @Override + public String getName(); + + /** + * Returns whether this file is read-only. + * This refinement of the corresponding IStorage and IResource + * methods links the semantics of read-only resources and read-only storage objects. + * + * @see IResource#isReadOnly() + * @see IStorage#isReadOnly() + */ + @SuppressWarnings("deprecation") + @Override + public boolean isReadOnly(); + + /** + * Moves this resource to be at the given location. + *

    + * This is a convenience method, fully equivalent to: + *

    +	 *   move(destination, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
    +	 * 
    + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file has been removed from its parent and a new file + * has been added to the parent of the destination. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • This resource is not local.
    • + *
    • The resource corresponding to the parent destination path does not exist.
    • + *
    • The resource corresponding to the parent destination path is a closed + * project.
    • + *
    • A resource at destination path does exist.
    • + *
    • A resource of a different type exists at the destination path.
    • + *
    • This resource is out of sync with the local file system + * and force is false.
    • + *
    • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IResource#move(IPath,int,IProgressMonitor) + * @see IResourceRuleFactory#moveRule(IResource, IResource) + */ + public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the charset for this file. Passing a value of null + * will remove the charset setting for this resource. + * + * @param newCharset a charset name, or null + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • An error happened while persisting this setting.
    • + *
    + * @see #getCharset() + * @since 3.0 + * @deprecated Replaced by {@link #setCharset(String, IProgressMonitor)} which + * is a workspace operation and reports changes in resource deltas. + */ + @Deprecated + public void setCharset(String newCharset) throws CoreException; + + /** + * Sets the charset for this file. Passing a value of null + * will remove the charset setting for this resource. + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's encoding has changed. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param newCharset a charset name, or null + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • An error happened while persisting this setting.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See {@link IResourceChangeEvent} for more details.
    • + *
    + * @see #getCharset() + * @see IResourceRuleFactory#charsetRule(IResource) + * @since 3.0 + */ + public void setCharset(String newCharset, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given input stream. + *

    + * This is a convenience method, fully equivalent to: + *

    +	 *   setContents(source, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
    +	 * 
    + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's contents have been changed. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param source an input stream containing the new contents of the file + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param keepHistory a flag indicating whether or not store + * the current contents in the local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • The corresponding location in the local file system + * is occupied by a directory.
    • + *
    • The workspace is not in sync with the corresponding location + * in the local file system and force is false.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    • The file modification validator disallowed the change.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #setContents(java.io.InputStream,int,IProgressMonitor) + */ + public void setContents(InputStream source, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given file state. + *

    + * This is a convenience method, fully equivalent to: + *

    +	 *   setContents(source, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
    +	 * 
    + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param source a previous state of this resource + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param keepHistory a flag indicating whether or not store + * the current contents in the local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • The state does not exist.
    • + *
    • The corresponding location in the local file system + * is occupied by a directory.
    • + *
    • The workspace is not in sync with the corresponding location + * in the local file system and force is false.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    • The file modification validator disallowed the change.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #setContents(IFileState,int,IProgressMonitor) + */ + public void setContents(IFileState source, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given input stream. + * The stream will get closed whether this method succeeds or fails. + * If the stream is null then the content is set to be the + * empty sequence of bytes. + *

    + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite a corresponding file in the local file system provided + * it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write a corresponding file in the local file system, overwriting any + * existing one if need be. In either case, if this method succeeds, the + * resource will be marked as being local (even if it wasn't before). + *

    + *

    + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of this file should be captured in the workspace's local + * history (properties are not recorded in the local history). The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying KEEP_HISTORY + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. This flag is ignored if the file was not previously local. + *

    + *

    + * Update flags other than FORCE and KEEP_HISTORY + * are ignored. + *

    + *

    + * Prior to modifying the contents of this file, the file modification validator (if provided + * by the VCM plug-in), will be given a chance to perform any last minute preparations. Validation + * is performed by calling IFileModificationValidator.validateSave on this file. + * If the validation fails, then this operation will fail. + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param source an input stream containing the new contents of the file + * @param updateFlags bit-wise or of update flag constants + * (FORCE and KEEP_HISTORY) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • The corresponding location in the local file system + * is occupied by a directory.
    • + *
    • The workspace is not in sync with the corresponding location + * in the local file system and FORCE is not specified.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    • The file modification validator disallowed the change.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void setContents(InputStream source, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given file state. + *

    + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite a corresponding file in the local file system provided + * it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write a corresponding file in the local file system, overwriting any + * existing one if need be. In either case, if this method succeeds, the + * resource will be marked as being local (even if it wasn't before). + *

    + *

    + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of this file should be captured in the workspace's local + * history (properties are not recorded in the local history). The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying KEEP_HISTORY + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. This flag is ignored if the file was not previously local. + *

    + *

    + * Update flags other than FORCE and KEEP_HISTORY + * are ignored. + *

    + *

    + * Prior to modifying the contents of this file, the file modification validator (if provided + * by the VCM plug-in), will be given a chance to perform any last minute preparations. Validation + * is performed by calling IFileModificationValidator.validateSave on this file. + * If the validation fails, then this operation will fail. + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param source a previous state of this resource + * @param updateFlags bit-wise or of update flag constants + * (FORCE and KEEP_HISTORY) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • The state does not exist.
    • + *
    • The corresponding location in the local file system + * is occupied by a directory.
    • + *
    • The workspace is not in sync with the corresponding location + * in the local file system and FORCE is not specified.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    • The file modification validator disallowed the change.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void setContents(IFileState source, int updateFlags, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java new file mode 100644 index 0000000000..bea045f65b --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * 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; + +import org.eclipse.core.resources.team.FileModificationValidator; +import org.eclipse.core.runtime.IStatus; + +/** + * The file modification validator is a Team-related hook for pre-checking operations + * that modify the contents of files. + *

    + * This interface is used only in conjunction with the + * "org.eclipse.core.resources.fileModificationValidator" + * extension point. It is intended to be implemented only + * by the Eclipse Platform Team plug-in. + *

    + * + * @since 2.0 + * @deprecated clients should subclass {@link FileModificationValidator} instead + * of implementing this interface + */ +@Deprecated +public interface IFileModificationValidator { + /** + * Validates that the given files can be modified. The files must all exist + * in the workspace. The optional context object may be supplied if + * UI-based validation is required. If the context is null, the + * validator must attempt to perform the validation in a headless manner. + * The returned status is IStatus.OK if this validator + * believes the given file can be modified. Other return statuses indicate + * the reason why the individual files cannot be modified. + * + * @param files the files that are to be modified; these files must all exist in the workspace + * @param context the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null if there is no UI context (declared + * as an Object to avoid any direct references on the SWT component) + * @return a status object that is OK if things are fine, otherwise a status describing + * reasons why modifying the given files is not reasonable + * @see IWorkspace#validateEdit(IFile[], Object) + */ + public IStatus validateEdit(IFile[] files, Object context); + + /** + * Validates that the given file can be saved. This method is called from + * IFile#setContents and IFile#appendContents + * before any attempt to write data to disk. The returned status is + * IStatus.OK if this validator believes the given file can be + * successfully saved. In all other cases the return value is a non-OK status. + * Note that a return value of IStatus.OK does not guarantee + * that the save will succeed. + * + * @param file the file that is to be modified; this file must exist in the workspace + * @return a status indicating whether or not it is reasonable to try writing to the given file; + * IStatus.OK indicates a save should be attempted. + * + * @see IFile#setContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + * @see IFile#appendContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus validateSave(IFile file); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java new file mode 100644 index 0000000000..1993294fbb --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.resources; + +import java.io.InputStream; +import org.eclipse.core.runtime.*; + +/** + * A previous state of a file stored in the workspace's local history. + *

    + * Certain methods for updating, deleting, or moving a file cause the + * "before" contents of the file to be copied to an internal area of the + * workspace called the local history area thus providing + * a limited history of earlier states of a file. + *

    + *

    + * Moving or copying a file will cause a copy of its local history to appear + * at the new location as well as at the original location. Subsequent + * changes to either file will only affect the local history of the file + * changed. Deleting a file and creating another one at the + * same path does not affect the history. If the original file had + * history, that same history will be available for the new one. + *

    + *

    + * The local history does not track resource properties. + * File states are volatile; the platform does not guarantee that a + * certain state will always be in the local history. + *

    + *

    + * File state objects implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

    + * + * @see IFile + * @see IStorage + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFileState extends IEncodedStorage, IAdaptable { + /** + * Returns whether this file state still exists in the local history. + * + * @return true if this state exists, and false + * if it does not + */ + public boolean exists(); + + /** + * Returns an open input stream on the contents of this file state. + * This refinement of the corresponding + * IStorage method returns an open input stream + * on the contents this file state represents. + * The client is responsible for closing the stream when finished. + * + * @return an input stream containing the contents of the file + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This state does not exist.
    • + *
    + */ + @Override + public InputStream getContents() throws CoreException; + + /** + * Returns the full path of this file state. + * This refinement of the corresponding IStorage + * method specifies that IFileStates always have a + * path and that path is the full workspace path of the file represented by this state. + * + * @see IResource#getFullPath() + * @see IStorage#getFullPath() + */ + @Override + public IPath getFullPath(); + + /** + * Returns the modification time of the file. If you create a file at + * 9:00 and modify it at 11:00, the file state added to the history + * at 11:00 will have 9:00 as its modification time. + *

    + * Note that is used only to give the user a general idea of how + * old this file state is. + * + * @return the time of last modification, in milliseconds since + * January 1, 1970, 00:00:00 GMT. + */ + public long getModificationTime(); + + /** + * Returns the name of this file state. + * This refinement of the corresponding IStorage + * method specifies that IFileStates always have a + * name and that name is equivalent to the last segment of the full path + * of the resource represented by this state. + * + * @see IResource#getName() + * @see IStorage#getName() + */ + @Override + public String getName(); + + /** + * Returns whether this file state is read-only. + * This refinement of the corresponding + * IStorage method restricts IFileStates to + * always be read-only. + * + * @see IStorage + */ + @Override + public boolean isReadOnly(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java new file mode 100644 index 0000000000..0328cf4e64 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2009 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 + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.resources.filtermatchers.AbstractFileInfoMatcher; + +/** + * A filter descriptor contains information about a filter type + * obtained from the plug-in manifest (plugin.xml) files. + *

    + * Filter descriptors are platform-defined objects that exist + * independent of whether that filter's bundle has been started. + *

    + * + * @see AbstractFileInfoMatcher + * @see IWorkspace#getFilterMatcherDescriptor(String) + * @see IWorkspace#getFilterMatcherDescriptors() + * @since 3.6 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFilterMatcherDescriptor { + + /** + * An argument filter type constant (value "filter"), denoting that this + * filter takes another filter as argument. + */ + public static final String ARGUMENT_TYPE_FILTER_MATCHER = "filterMatcher"; //$NON-NLS-1$ + /** + * An argument filter type constant (value "filters"), denoting that this + * filter takes an array of other filters as argument. + */ + public static final String ARGUMENT_TYPE_FILTER_MATCHERS = "filterMatchers"; //$NON-NLS-1$ + /** + * An argument filter type constant (value "none"), denoting that this + * filter does not take any arguments. + */ + public static final String ARGUMENT_TYPE_NONE = "none"; //$NON-NLS-1$ + /** + * An argument filter type constant (value "string"), denoting that this + * filter takes a string argument + */ + public static final String ARGUMENT_TYPE_STRING = "string"; //$NON-NLS-1$ + + /** + * Returns the argument type expected by this filter. The result will be one of the + * ARGUMENT_TYPE_* constants declared on this class. + * @return The argument type of this filter extension + */ + public abstract String getArgumentType(); + + /** + * Returns a translated, human-readable description for this filter extension. + * @return The human-readable filter description + */ + public abstract String getDescription(); + + /** + * Returns the fully qualified id of the filter extension. + * @return The fully qualified id of the filter extension. + */ + public abstract String getId(); + + /** + * Returns a translated, human-readable name for this filter extension. + * @return The human-readable filter name + */ + public abstract String getName(); + + /** + * TODO What is this? + */ + public abstract boolean isFirstOrdering(); + +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java new file mode 100644 index 0000000000..d1efd1a8f6 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java @@ -0,0 +1,461 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import org.eclipse.core.runtime.*; + +/** + * Folders may be leaf or non-leaf resources and may contain files and/or other folders. + * A folder resource is stored as a directory in the local file system. + *

    + * Folders, like other resource types, may exist in the workspace but + * not be local; non-local folder resources serve as place-holders for + * folders whose properties have not yet been fetched from a repository. + *

    + *

    + * Folders implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

    + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFolder extends IContainer, IAdaptable { + + /** + * Creates a new folder resource as a member of this handle's parent resource. + *

    + * This is a convenience method, fully equivalent to: + *

    +	 *   create((force ? FORCE : IResource.NONE), local, monitor);
    +	 * 
    + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param local a flag controlling whether or not the folder will be local + * after the creation + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource already exists in the workspace.
    • + *
    • The workspace contains a resource of a different type + * at the same path as this resource.
    • + *
    • The parent of this resource does not exist.
    • + *
    • The parent of this resource is a project that is not open.
    • + *
    • The parent contains a resource of a different type + * at the same path as this resource.
    • + *
    • The parent of this resource is virtual, but this resource is not.
    • + *
    • The name of this resource is not valid (according to + * IWorkspace.validateName).
    • + *
    • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
    • + *
    • The corresponding location in the local file system is occupied + * by a folder and force is false.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IFolder#create(int,boolean,IProgressMonitor) + */ + public void create(boolean force, boolean local, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new folder resource as a member of this handle's parent resource. + *

    + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to create a directory in the local file system if there isn't one already. + * This option ensures there is no unintended data loss; it is the recommended + * setting. However, if FORCE is specified, this method will + * be deemed a success even if there already is a corresponding directory. + *

    + *

    + * The {@link IResource#DERIVED} update flag indicates that this resource + * should immediately be set as a derived resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setDerived(boolean)} + * with a value of true immediately after creating the resource. + *

    + *

    + * The {@link IResource#TEAM_PRIVATE} update flag indicates that this resource + * should immediately be set as a team private resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setTeamPrivateMember(boolean)} + * with a value of true immediately after creating the resource. + *

    + *

    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

    + *

    + * Update flags other than those listed above are ignored. + *

    + *

    + * This method synchronizes this resource with the local file system. + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#FORCE}, {@link IResource#DERIVED}, {@link IResource#TEAM_PRIVATE}) + * and {@link IResource#VIRTUAL}) + * @param local a flag controlling whether or not the folder will be local + * after the creation + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource already exists in the workspace.
    • + *
    • The workspace contains a resource of a different type + * at the same path as this resource.
    • + *
    • The parent of this resource does not exist.
    • + *
    • The parent of this resource is a project that is not open.
    • + *
    • The parent contains a resource of a different type + * at the same path as this resource.
    • + *
    • The parent of this resource is virtual, but this resource is not.
    • + *
    • The name of this resource is not valid (according to + * IWorkspace.validateName).
    • + *
    • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
    • + *
    • The corresponding location in the local file system is occupied + * by a folder and FORCE is not specified.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#createRule(IResource) + * @since 2.0 + */ + public void create(int updateFlags, boolean local, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new folder resource as a member of this handle's parent resource. + * The folder's contents will be located in the directory specified by the given + * file system path. The given path must be either an absolute file system + * path, or a relative path whose first segment is the name of a workspace path + * variable. + *

    + * The ALLOW_MISSING_LOCAL update flag controls how this + * method deals with cases where the local file system directory to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If ALLOW_MISSING_LOCAL is specified, the operation will succeed + * even if the local directory is missing, or the path is relative to an + * undefined variable. If ALLOW_MISSING_LOCAL is not specified, the + * operation will fail in the case where the local file system directory does + * not exist or the path is relative to an undefined variable. + *

    + *

    + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then the existing linked resource's location is replaced + * by localLocation's value. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

    + *

    + * The {@link IResource#BACKGROUND_REFRESH} update flag controls how + * this method synchronizes the new resource with the filesystem. If this flag is + * specified, resources on disk will be synchronized in the background after the + * method returns. Child resources of the link may not be available until + * this background refresh completes. If this flag is not specified, resources are + * synchronized in the foreground before this method returns. + *

    + *

    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

    + *

    + * Update flags other than those listed above are ignored. + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param localLocation a file system path where the folder should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE}, {@link IResource#BACKGROUND_REFRESH}, and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource already exists in the workspace.
    • + *
    • The workspace contains a resource of a different type + * at the same path as this resource.
    • + *
    • The parent of this resource does not exist.
    • + *
    • The parent of this resource is not an open project
    • + *
    • The name of this resource is not valid (according to + * IWorkspace.validateName).
    • + *
    • The corresponding location in the local file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
    • + *
    • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    • The team provider for the project which contains this folder does not permit + * linked resources.
    • + *
    • This folder's project contains a nature which does not permit linked resources.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#BACKGROUND_REFRESH + * @see IResource#HIDDEN + * @since 2.1 + */ + public void createLink(IPath localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new folder resource as a member of this handle's parent resource. + * The folder's contents will be located in the directory specified by the given + * file system URI. The given URI must be either absolute, or a relative URI + * whose first path segment is the name of a workspace path variable. + *

    + * The ALLOW_MISSING_LOCAL update flag controls how this + * method deals with cases where the local file system directory to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If ALLOW_MISSING_LOCAL is specified, the operation will succeed + * even if the local directory is missing, or the path is relative to an + * undefined variable. If ALLOW_MISSING_LOCAL is not specified, the + * operation will fail in the case where the local file system directory does + * not exist or the path is relative to an undefined variable. + *

    + *

    + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then any existing resource with the same name is removed + * from the workspace to make way for creation of the link. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

    + *

    + * The {@link IResource#BACKGROUND_REFRESH} update flag controls how + * this method synchronizes the new resource with the filesystem. If this flag is + * specified, resources on disk will be synchronized in the background after the + * method returns. Child resources of the link may not be available until + * this background refresh completes. If this flag is not specified, resources are + * synchronized in the foreground before this method returns. + *

    + *

    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

    + *

    + * Update flags other than those listed above are ignored. + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param location a file system path where the folder should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE}, {@link IResource#BACKGROUND_REFRESH}, and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource already exists in the workspace.
    • + *
    • The workspace contains a resource of a different type + * at the same path as this resource.
    • + *
    • The parent of this resource does not exist.
    • + *
    • The parent of this resource is not an open project
    • + *
    • The name of this resource is not valid (according to + * IWorkspace.validateName).
    • + *
    • The corresponding location in the local file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
    • + *
    • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    • The team provider for the project which contains this folder does not permit + * linked resources.
    • + *
    • This folder's project contains a nature which does not permit linked resources.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#BACKGROUND_REFRESH + * @see IResource#HIDDEN + * @since 3.2 + */ + public void createLink(URI location, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this resource from the workspace. + *

    + * This is a convenience method, fully equivalent to: + *

    +	 *   delete((keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
    +	 * 
    + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this folder has been removed from its parent. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
      + *
    • This resource could not be deleted for some reason.
    • + *
    • This resource is out of sync with the local file system + * and force is false.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IResourceRuleFactory#deleteRule(IResource) + * @see IResource#delete(int,IProgressMonitor) + */ + public void delete(boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Returns a handle to the file with the given name in this folder. + *

    + * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

    + * + * @param name the string name of the member file + * @return the (handle of the) member file + * @see #getFolder(String) + */ + public IFile getFile(String name); + + /** + * Returns a handle to the folder with the given name in this folder. + *

    + * This is a resource handle operation; neither the container + * nor the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

    + * + * @param name the string name of the member folder + * @return the (handle of the) member folder + * @see #getFile(String) + */ + public IFolder getFolder(String name); + + /** + * Moves this resource so that it is located at the given path. + *

    + * This is a convenience method, fully equivalent to: + *

    +	 *   move(destination, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
    +	 * 
    + *

    + *

    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this folder has been removed from its parent and a new folder + * has been added to the parent of the destination. + *

    + *

    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

    + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
      + *
    • This resource does not exist.
    • + *
    • This resource or one of its descendents is not local.
    • + *
    • The resource corresponding to the parent destination path does not exist.
    • + *
    • The resource corresponding to the parent destination path is a closed + * project.
    • + *
    • A resource at destination path does exist.
    • + *
    • A resource of a different type exists at the destination path.
    • + *
    • This resource or one of its descendents is out of sync with the local file system + * and force is false.
    • + *
    • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
    • + *
    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
    • + *
    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @see IResource#move(IPath,int,IProgressMonitor) + */ + public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java new file mode 100644 index 0000000000..14fe1bed8f --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java @@ -0,0 +1,578 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Map; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; + +/** + * Markers are a general mechanism for associating notes and meta-data with + * resources. + *

    + * Markers themselves are handles in the same way as IResources + * are handles. Instances of IMarker do not hold the attributes + * themselves but rather uniquely refer to the attribute container. As such, + * their state may change underneath the handle with no warning to the holder + * of the handle. + *

    + * The Resources plug-in provides a general framework for + * defining and manipulating markers and provides several standard marker types. + *

    + *

    + * Each marker has:

      + *
    • a type string, specifying its type (e.g. + * "org.eclipse.core.resources.taskmarker"),
    • + *
    • an identifier which is unique (relative to a particular resource)
    • + *
    + * Specific types of markers may carry additional information. + *

    + *

    + * The resources plug-in defines five standard types: + *

      + *
    • org.eclipse.core.resources.marker
    • + *
    • org.eclipse.core.resources.taskmarker
    • + *
    • org.eclipse.core.resources.problemmarker
    • + *
    • org.eclipse.core.resources.bookmark
    • + *
    • org.eclipse.core.resources.textmarker
    • + *
    + * The plug-in also provides an extension point ( + * org.eclipse.core.resources.markers) into which other + * plug-ins can install marker type declaration extensions. + *

    + *

    + * Marker types are declared within a multiple inheritance type system. + * New markers are defined in the plugin.xml file of the + * declaring plug-in. A valid declaration contains elements as defined by + * the extension point DTD: + *

      + *
    • type - the unique name of the marker type
    • + *
    • super - the list of marker types of which this marker is to be considered a sub-type
    • + *
    • attributes - the list of standard attributes which may be present on this type of marker
    • + *
    • persistent - whether markers of this type should be persisted by the platform
    • + * + *

      + *

      All markers declared as persistent are saved when the + * workspace is saved, except those explicitly set as transient (the + * TRANSIENT attribute is set as true). A plug-in + * which defines a persistent marker is not directly involved in saving and + * restoring the marker. Markers are not under version and configuration + * management, and cannot be shared via VCM repositories. + *

      + *

      + * Markers implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

      + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IMarker extends IAdaptable { + + /*==================================================================== + * Marker types: + *====================================================================*/ + + /** + * Base marker type. + * + * @see #getType() + */ + public static final String MARKER = ResourcesPlugin.PI_RESOURCES + ".marker"; //$NON-NLS-1$ + + /** + * Task marker type. + * + * @see #getType() + */ + public static final String TASK = ResourcesPlugin.PI_RESOURCES + ".taskmarker"; //$NON-NLS-1$ + + /** + * Problem marker type. + * + * @see #getType() + */ + public static final String PROBLEM = ResourcesPlugin.PI_RESOURCES + ".problemmarker"; //$NON-NLS-1$ + + /** + * Text marker type. + * + * @see #getType() + */ + public static final String TEXT = ResourcesPlugin.PI_RESOURCES + ".textmarker"; //$NON-NLS-1$ + + /** + * Bookmark marker type. + * + * @see #getType() + */ + public static final String BOOKMARK = ResourcesPlugin.PI_RESOURCES + ".bookmark"; //$NON-NLS-1$ + + /*==================================================================== + * Marker attributes: + *====================================================================*/ + + /** + * Severity marker attribute. A number from the set of error, warning and info + * severities defined by the platform. + * + * @see #SEVERITY_ERROR + * @see #SEVERITY_WARNING + * @see #SEVERITY_INFO + * @see #getAttribute(String, int) + */ + public static final String SEVERITY = "severity"; //$NON-NLS-1$ + + /** + * Message marker attribute. A localized string describing the nature + * of the marker (e.g., a name for a bookmark or task). The content + * and form of this attribute is not specified or interpreted by the platform. + * + * @see #getAttribute(String, String) + */ + public static final String MESSAGE = "message"; //$NON-NLS-1$ + + /** + * Location marker attribute. The location is a human-readable (localized) string which + * can be used to distinguish between markers on a resource. As such it + * should be concise and aimed at users. The content and + * form of this attribute is not specified or interpreted by the platform. + * + * @see #getAttribute(String, String) + */ + public static final String LOCATION = "location"; //$NON-NLS-1$ + + /** + * Priority marker attribute. A number from the set of high, normal and low + * priorities defined by the platform. + * + * @see #PRIORITY_HIGH + * @see #PRIORITY_NORMAL + * @see #PRIORITY_LOW + * @see #getAttribute(String, int) + */ + public static final String PRIORITY = "priority"; //$NON-NLS-1$ + + /** + * Done marker attribute. A boolean value indicating whether + * the marker (e.g., a task) is considered done. + * + * @see #getAttribute(String, String) + */ + public static final String DONE = "done"; //$NON-NLS-1$ + + /** + * Character start marker attribute. An integer value indicating where a text + * marker starts. This attribute is zero-relative and inclusive. + * + * @see #getAttribute(String, String) + */ + public static final String CHAR_START = "charStart"; //$NON-NLS-1$ + + /** + * Character end marker attribute. An integer value indicating where a text + * marker ends. This attribute is zero-relative and exclusive. + * + * @see #getAttribute(String, String) + */ + public static final String CHAR_END = "charEnd"; //$NON-NLS-1$ + + /** + * Line number marker attribute. An integer value indicating the line number + * for a text marker. This attribute is 1-relative. + * + * @see #getAttribute(String, String) + */ + public static final String LINE_NUMBER = "lineNumber"; //$NON-NLS-1$ + + /** + * Transient marker attribute. A boolean value indicating whether the + * marker (e. g., a task) is considered transient even if its type is + * declared as persistent. + * + * @see #getAttribute(String, String) + * @since 2.1 + */ + public static final String TRANSIENT = "transient"; //$NON-NLS-1$ + + /** + * User editable marker attribute. A boolean value indicating whether a + * user should be able to manually change the marker (e.g. a task). The + * default is true. Note that the value of this attribute + * is to be used by the UI as a suggestion and its value will NOT be + * interpreted by Core in any manner and will not be enforced by Core + * when performing any operations on markers. + * + * @see #getAttribute(String, String) + * @since 2.1 + */ + public static final String USER_EDITABLE = "userEditable"; //$NON-NLS-1$ + + /** + * Source id attribute. A string attribute that can be used by tools that + * generate markers to indicate the source of the marker. Use of this attribute is + * optional and its format or existence is not enforced. This attribute is + * intended to improve serviceability by providing a value that product support + * personnel or automated tools can use to determine appropriate help and + * resolutions for markers. + * + * @see #getAttribute(String, String) + * @since 3.3 + */ + public static final String SOURCE_ID = "sourceId"; //$NON-NLS-1$ + + /*==================================================================== + * Marker attributes values: + *====================================================================*/ + + /** + * High priority constant (value 2). + * + * @see #getAttribute(String, int) + */ + public static final int PRIORITY_HIGH = 2; + + /** + * Normal priority constant (value 1). + * + * @see #getAttribute(String, int) + */ + public static final int PRIORITY_NORMAL = 1; + + /** + * Low priority constant (value 0). + * + * @see #getAttribute(String, int) + */ + public static final int PRIORITY_LOW = 0; + + /** + * Error severity constant (value 2) indicating an error state. + * + * @see #getAttribute(String, int) + */ + public static final int SEVERITY_ERROR = 2; + + /** + * Warning severity constant (value 1) indicating a warning. + * + * @see #getAttribute(String, int) + */ + public static final int SEVERITY_WARNING = 1; + + /** + * Info severity constant (value 0) indicating information only. + * + * @see #getAttribute(String, int) + */ + public static final int SEVERITY_INFO = 0; + + /** + * Deletes this marker from its associated resource. This method has no + * effect if this marker does not exist. + * + * @exception CoreException if this marker could not be deleted. Reasons include: + *
        + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void delete() throws CoreException; + + /** + * Tests this marker for equality with the given object. + * Two markers are equal if their id and resource are both equal. + * + * @param object the other object + * @return an indication of whether the objects are equal + */ + @Override + public boolean equals(Object object); + + /** + * Returns whether this marker exists in the workspace. A marker + * exists if its resource exists and has a marker with the marker's id. + * + * @return true if this marker exists, otherwise + * false + */ + public boolean exists(); + + /** + * Returns the attribute with the given name. The result is an instance of one + * of the following classes: String, Integer, + * or Boolean. + * Returns null if the attribute is undefined. + * + * @param attributeName the name of the attribute + * @return the value, or null if the attribute is undefined. + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This marker does not exist.
      • + *
      + */ + public Object getAttribute(String attributeName) throws CoreException; + + /** + * Returns the integer-valued attribute with the given name. + * Returns the given default value if the attribute is undefined. + * or the marker does not exist or is not an integer value. + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if no value is found + * @return the value or the default value if no value was found. + */ + public int getAttribute(String attributeName, int defaultValue); + + /** + * Returns the string-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or the marker does not exist or is not a string value. + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if no value is found + * @return the value or the default value if no value was found. + */ + public String getAttribute(String attributeName, String defaultValue); + + /** + * Returns the boolean-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or the marker does not exist or is not a boolean value. + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if no value is found + * @return the value or the default value if no value was found. + */ + public boolean getAttribute(String attributeName, boolean defaultValue); + + /** + * Returns a map with all the attributes for the marker. + * If the marker has no attributes then null is returned. + * + * @return a map of attribute keys and values (key type : String + * value type : String, Integer, or + * Boolean) or null. + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This marker does not exist.
      • + *
      + */ + public Map getAttributes() throws CoreException; + + /** + * Returns the attributes with the given names. The result is an an array + * whose elements correspond to the elements of the given attribute name + * array. Each element is null or an instance of one + * of the following classes: String, Integer, + * or Boolean. + * + * @param attributeNames the names of the attributes + * @return the values of the given attributes. + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This marker does not exist.
      • + *
      + */ + public Object[] getAttributes(String[] attributeNames) throws CoreException; + + /** + * Returns the time at which this marker was created. + * + * @return the difference, measured in milliseconds, between the time at which + * this marker was created and midnight, January 1, 1970 UTC, or 0L + * if the creation time is not known (this can occur in workspaces created using v2.0 or earlier). + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This marker does not exist.
      • + *
      + * @since 2.1 + */ + public long getCreationTime() throws CoreException; + + /** + * Returns the id of the marker. The id of a marker is unique + * relative to the resource with which the marker is associated. + * Marker ids are not globally unique. + * + * @return the id of the marker + * @see IResource#findMarker(long) + */ + public long getId(); + + /** + * Returns the resource with which this marker is associated. + * + * @return the resource with which this marker is associated + */ + public IResource getResource(); + + /** + * Returns the type of this marker. The returned marker type will not be + * null. + * + * @return the type of this marker + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This marker does not exist.
      • + *
      + */ + public String getType() throws CoreException; + + /** + * Returns whether the type of this marker is considered to be a sub-type of + * the given marker type. + * + * @return boolean trueif the marker's type + * is the same as (or a sub-type of) the given type. + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This marker does not exist.
      • + *
      + */ + public boolean isSubtypeOf(String superType) throws CoreException; + + /** + * Sets the integer-valued attribute with the given name. + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

      + * + * @param attributeName the name of the attribute + * @param value the value + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This marker does not exist.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttribute(String attributeName, int value) throws CoreException; + + /** + * Sets the attribute with the given name. The value must be null or + * an instance of one of the following classes: + * String, Integer, or Boolean. + * If the value is null, the attribute is considered to be undefined. + * + *

      + * The attribute value cannot be String + * whose UTF encoding exceeds 65535 bytes. On persistent + * markers this limit is enforced by an assertion. + *

      + * + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

      + * + * @param attributeName the name of the attribute + * @param value the value, or null if the attribute is to be undefined + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This marker does not exist.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttribute(String attributeName, Object value) throws CoreException; + + /** + * Sets the boolean-valued attribute with the given name. + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

      + * + * @param attributeName the name of the attribute + * @param value the value + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This marker does not exist.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttribute(String attributeName, boolean value) throws CoreException; + + /** + * Sets the given attribute key-value pairs on this marker. + * The values must be null or an instance of + * one of the following classes: String, + * Integer, or Boolean. + * If a value is null, the new value of the + * attribute is considered to be undefined. + * + *

      + * The values of the attributes cannot be String + * whose UTF encoding exceeds 65535 bytes. On persistent markers + * this limit is enforced by an assertion. + *

      + * + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

      + * + * @param attributeNames an array of attribute names + * @param values an array of attribute values + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This marker does not exist.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttributes(String[] attributeNames, Object[] values) throws CoreException; + + /** + * Sets the attributes for this marker to be the ones contained in the + * given table. The values must be an instance of one of the following classes: + * String, Integer, or Boolean. + * Attributes previously set on the marker but not included in the given map + * are considered to be removals. Setting the given map to be null + * is equivalent to removing all marker attributes. + * + *

      + * The values of the attributes cannot be String + * whose UTF encoding exceeds 65535 bytes. On persistent markers + * this limit is enforced by an assertion. + *

      + * + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

      + * + * @param attributes a map of attribute names to attribute values + * (key type : String value type : String, + * Integer, or Boolean) or null + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This marker does not exist.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttributes(Map attributes) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java new file mode 100644 index 0000000000..071c823bcc --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Map; + +/** + * A marker delta describes the change to a single marker. + * A marker can either be added, removed or changed. + * Marker deltas give access to the state of the marker as it + * was (in the case of deletions and changes) before the modifying + * operation occurred. + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IMarkerDelta { + /** + * Returns the object attribute with the given name. The result is an instance of one + * of the following classes: String, Integer, + * or Boolean. + * Returns null if the attribute is undefined. + * The set of valid attribute names is defined elsewhere. + *

      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

      + * @param attributeName the name of the attribute + * @return the value, or null if the attribute is undefined. + */ + public Object getAttribute(String attributeName); + + /** + * Returns the integer-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or is not an integer value. + *

      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

      + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if the attribute does not exist + * @return the value or the default value if the attribute is undefined. + */ + public int getAttribute(String attributeName, int defaultValue); + + /** + * Returns the string-valued attribute with the given name. + * Returns the given default value if the attribute is undefined or + * is not a string value. + *

      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

      + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if the attribute does not exist + * @return the value or the default value if the attribute is undefined. + */ + public String getAttribute(String attributeName, String defaultValue); + + /** + * Returns the boolean-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or is not a boolean value. + *

      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

      + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if the attribute does not exist + * @return the value or the default value if the attribute is undefined. + */ + public boolean getAttribute(String attributeName, boolean defaultValue); + + /** + * Returns a Map with all the attributes for the marker. The result is a Map + * whose keys are attributes names and whose values are attribute values. + * Each value an instance of one of the following classes: String, + * Integer, or Boolean. If the marker has no + * attributes then null is returned. + *

      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

      + * + * @return a map of attribute keys and values (key type : String + * value type : String, Integer, or + * Boolean) or null. + */ + public Map getAttributes(); + + /** + * Returns the attributes with the given names. The result is an array + * whose elements correspond to the elements of the given attribute name + * array. Each element is null or an instance of one + * of the following classes: String, Integer, + * or Boolean. + *

      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

      + * + * @param attributeNames the names of the attributes + * @return the values of the given attributes. + */ + public Object[] getAttributes(String[] attributeNames); + + /** + * Returns the id of the marker. The id of a marker is unique + * relative to the resource with which the marker is associated. + * Marker ids are not globally unique. + * + * @return the id of the marker + */ + public long getId(); + + /** + * Returns the kind of this marker delta: + * one of IResourceDelta.ADDED, + * IResourceDelta.REMOVED, or IResourceDelta.CHANGED. + * + * @return the kind of marker delta + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + */ + public int getKind(); + + /** + * Returns the marker described by this change. + * If kind is IResourceDelta.REMOVED, then this is the old marker, + * otherwise this is the new marker. Note that if the marker was deleted, + * the value returned cannot be used to access attributes. + * + * @return the marker + */ + public IMarker getMarker(); + + /** + * Returns the resource with which this marker is associated. + * + * @return the resource + */ + public IResource getResource(); + + /** + * Returns the type of this marker. + *

      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

      + * + * @return the type of this marker + */ + public String getType(); + + /** + * Returns whether the type of this marker is considered to be a sub-type of + * the given marker type. + *

      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

      + * + * @return boolean trueif the marker's type + * is the same as (or a sub-type of) the given type. + */ + public boolean isSubtypeOf(String superType); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java new file mode 100644 index 0000000000..8f70167495 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; + +/** + * Describes a change in a path variable. The change may denote that a + * variable has been created, deleted or had its value changed. + * + * @since 2.1 + * @see IPathVariableChangeListener + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IPathVariableChangeEvent { + + /** Event type constant (value = 1) that denotes a value change . */ + public final static int VARIABLE_CHANGED = 1; + + /** Event type constant (value = 2) that denotes a variable creation. */ + public final static int VARIABLE_CREATED = 2; + + /** Event type constant (value = 3) that denotes a variable deletion. */ + public final static int VARIABLE_DELETED = 3; + + /** + * Returns the variable's current value. If the event type is + * VARIABLE_CHANGED then it is the new value, if the event + * type is VARIABLE_CREATED then it is the new value, or + * if the event type is VARIABLE_DELETED then it will + * be null. + * + * @return the variable's current value, or null + */ + public IPath getValue(); + + /** + * Returns the affected variable's name. + * + * @return the affected variable's name + */ + public String getVariableName(); + + /** + * Returns an object identifying the source of this event. + * + * @return an object identifying the source of this event + * @see java.util.EventObject + */ + public Object getSource(); + + /** + * Returns the type of event being reported. + * + * @return one of the event type constants + * @see #VARIABLE_CHANGED + * @see #VARIABLE_CREATED + * @see #VARIABLE_DELETED + */ + public int getType(); + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java new file mode 100644 index 0000000000..5221232e20 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.util.EventListener; + +/** + * An interface to be implemented by objects interested in path variable + * creation, removal and value change events. + * + *

      Clients may implement this interface.

      + * + * @since 2.1 + */ +public interface IPathVariableChangeListener extends EventListener { + /** + * Notification that a path variable has changed. + *

      + * This method is called when a path variable is added, removed or has its value + * changed in the observed IPathVariableManager object. + *

      + * + * @param event the path variable change event object describing which variable + * changed and how + * @see IPathVariableManager#addChangeListener(IPathVariableChangeListener) + * @see IPathVariableManager#removeChangeListener(IPathVariableChangeListener) + * @see IPathVariableChangeEvent + */ + public void pathVariableChanged(IPathVariableChangeEvent event); + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java new file mode 100644 index 0000000000..ce9931b199 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java @@ -0,0 +1,352 @@ +/******************************************************************************* + * 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 + * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support + *******************************************************************************/ + +package org.eclipse.core.resources; + +import java.net.URI; +import org.eclipse.core.runtime.*; + +/** + * Manages a collection of path variables and resolves paths containing a + * variable reference. + *

      + * A path variable is a pair of non-null elements (name,value) where name is + * a case-sensitive string (containing only letters, digits and the underscore + * character, and not starting with a digit), and value is an absolute + * IPath object. + *

      + *

      + * Path variables allow for the creation of relative paths whose exact + * location in the file system depends on the value of a variable. A variable + * reference may only appear as the first segment of a relative path. + *

      + * + * @see org.eclipse.core.runtime.IPath + * @since 2.1 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IPathVariableManager { + + /** + * Converts an absolute path to path relative to some defined + * variable. For example, converts "C:/foo/bar.txt" into "FOO/bar.txt", + * granted that the path variable "FOO" value is "C:/foo". + *

      + * The "force" argument will cause an intermediate path variable to be created if + * the given path can be relative only to a parent of an existing path variable. + * For example, if the path "C:/other/file.txt" is to be converted + * and no path variables point to "C:/" or "C:/other" but "FOO" + * points to "C:/foo", an intermediate "OTHER" variable will be + * created relative to "FOO" containing the value "${PARENT-1-FOO}" + * so that the final path returned will be "OTHER/file.txt". + *

      + *

      + * The argument "variableHint" can be used to specify the name of the path + * variable to make the provided path relative to. + *

      + * + * @param path The absolute path to be converted + * @param force indicates whether intermediate path variables should be created + * if the path is relative only to a parent of an existing path variable. + * @param variableHint The name of the variable to which the path should be made + * relative to, or null for the nearest one. + * @return The converted path + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • The variable name is not valid
      • + *
      + * @since 3.6 + */ + public URI convertToRelative(URI path, boolean force, String variableHint) throws CoreException; + + /** + * Sets the path variable with the given name to be the specified value. + * Depending on the value given and if the variable is currently defined + * or not, there are several possible outcomes for this operation: + *

      + *

        + *
      • A new variable will be created, if there is no variable defined with + * the given name, and the given value is not null. + *
      • + * + *
      • The referred variable's value will be changed, if it already exists + * and the given value is not null.
      • + * + *
      • The referred variable will be removed, if a variable with the given + * name is currently defined and the given value is null. + *
      • + * + *
      • The call will be ignored, if a variable with the given name is not + * currently defined and the given value is null, or if it is + * defined but the given value is equal to its current value. + *
      • + *
      + *

      If a variable is effectively changed, created or removed by a call to + * this method, notification will be sent to all registered listeners.

      + * + * @param name the name of the variable + * @param value the value for the variable (may be null) + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • The variable name is not valid
      • + *
      • The variable value is relative
      • + *
      + * @deprecated use {@link #setURIValue(String, URI)} instead. + */ + @Deprecated + public void setValue(String name, IPath value) throws CoreException; + + /** + * Sets the path variable with the given name to be the specified value. + * Depending on the value given and if the variable is currently defined + * or not, there are several possible outcomes for this operation: + *

      + *

        + *
      • A new variable will be created, if there is no variable defined with + * the given name, and the given value is not null. + *
      • + * + *
      • The referred variable's value will be changed, if it already exists + * and the given value is not null.
      • + * + *
      • The referred variable will be removed, if a variable with the given + * name is currently defined and the given value is null. + *
      • + * + *
      • The call will be ignored, if a variable with the given name is not + * currently defined and the given value is null, or if it is + * defined but the given value is equal to its current value. + *
      • + *
      + *

      If a variable is effectively changed, created or removed by a call to + * this method, notification will be sent to all registered listeners.

      + * + * @param name the name of the variable + * @param value the value for the variable (may be null) + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • The variable name is not valid
      • + *
      • The variable value is relative
      • + *
      + * @since 3.6 + */ + public void setURIValue(String name, URI value) throws CoreException; + + /** + * Returns the value of the path variable with the given name. If there is + * no variable defined with the given name, returns null. + * + * @param name the name of the variable to return the value for + * @return the value for the variable, or null if there is no + * variable defined with the given name + * @deprecated use {@link #getURIValue(String)} instead. + */ + @Deprecated + public IPath getValue(String name); + + /** + * Returns the value of the path variable with the given name. If there is + * no variable defined with the given name, returns null. + * + * @param name the name of the variable to return the value for + * @return the value for the variable, or null if there is no + * variable defined with the given name + * @since 3.6 + */ + public URI getURIValue(String name); + + /** + * Returns an array containing all defined path variable names. + * + * @return an array containing all defined path variable names + */ + public String[] getPathVariableNames(); + + // Should be added for 3.6 + // public String[] getPathVariableNames(String name); + + /** + * Registers the given listener to receive notification of changes to path + * variables. The listener will be notified whenever a variable has been + * added, removed or had its value changed. Has no effect if an identical + * path variable change listener is already registered. + * + * @param listener the listener + * @see IPathVariableChangeListener + */ + public void addChangeListener(IPathVariableChangeListener listener); + + /** + * Removes the given path variable change listener from the listeners list. + * Has no effect if an identical listener is not registered. + * + * @param listener the listener + * @see IPathVariableChangeListener + */ + public void removeChangeListener(IPathVariableChangeListener listener); + + /** + * Resolves a relative URI object potentially containing a + * variable reference as its first segment, replacing the variable reference + * (if any) with the variable's value (which is a concrete absolute URI). + * If the given URI is absolute or has a non- null device then + * no variable substitution is done and that URI is returned as is. If the + * given URI is relative and has a null device, but the first + * segment does not correspond to a defined variable, then the URI is + * returned as is. + *

      + * If the given URI is null then null will be + * returned. In all other cases the result will be non-null. + *

      + * + * @param uri the URI to be resolved + * @return the resolved URI or null + * @since 3.2 + */ + public URI resolveURI(URI uri); + + /** + * Resolves a relative IPath object potentially containing a + * variable reference as its first segment, replacing the variable reference + * (if any) with the variable's value (which is a concrete absolute path). + * If the given path is absolute or has a non- null device then + * no variable substitution is done and that path is returned as is. If the + * given path is relative and has a null device, but the first + * segment does not correspond to a defined variable, then the path is + * returned as is. + *

      + * If the given path is null then null will be + * returned. In all other cases the result will be non-null. + *

      + * + *

      + * For example, consider the following collection of path variables: + *

      + *
        + *
      • TEMP = c:/temp
      • + *
      • BACKUP = /tmp/backup
      • + *
      + *

      The following paths would be resolved as: + *

      c:/bin => c:/bin

      + *

      c:TEMP => c:TEMP

      + *

      /TEMP => /TEMP

      + *

      TEMP => c:/temp

      + *

      TEMP/foo => c:/temp/foo

      + *

      BACKUP => /tmp/backup

      + *

      BACKUP/bar.txt => /tmp/backup/bar.txt

      + *

      SOMEPATH/foo => SOMEPATH/foo

      + * + * @param path the path to be resolved + * @return the resolved path or null + * @deprecated use {@link #resolveURI(URI)} instead. + */ + @Deprecated + public IPath resolvePath(IPath path); + + /** + * Returns true if the given variable is defined and + * false otherwise. Returns false if the given + * name is not a valid path variable name. + * + * @param name the variable's name + * @return true if the variable exists, false + * otherwise + */ + public boolean isDefined(String name); + + /** + * Returns whether a variable is user defined or not. + * + * @return true if the path is user defined. + * @since 3.6 + */ + public boolean isUserDefined(String name); + + /** + * Validates the given name as the name for a path variable. A valid path + * variable name is made exclusively of letters, digits and the underscore + * character, and does not start with a digit. + * + * @param name a possibly valid path variable name + * @return a status object with code IStatus.OK if + * the given name is a valid path variable name, otherwise a status + * object indicating what is wrong with the string + * @see IStatus#OK + */ + public IStatus validateName(String name); + + /** + * Validates the given path as the value for a path variable. A path + * variable value must be a valid path that is absolute. + * + * @param path a possibly valid path variable value + * @return a status object with code IStatus.OK if the given + * path is a valid path variable value, otherwise a status object indicating + * what is wrong with the value + * @see IPath#isValidPath(String) + * @see IStatus#OK + */ + public IStatus validateValue(IPath path); + + /** + * Validates the given path as the value for a path variable. A path + * variable value must be a valid path that is absolute. + * + * @param path a possibly valid path variable value + * @return a status object with code {@link IStatus#OK} if the given + * path is a valid path variable value, otherwise a status object indicating + * what is wrong with the value + * @see IPath#isValidPath(String) + * @see IStatus#OK + * @since 3.6 + */ + public IStatus validateValue(URI path); + + /** + * Returns a variable relative path equivalent to an absolute path for a + * file or folder in the file system, according to the variables defined in + * this project PathVariableManager. The file or folder need not to exist. + * + * @param location + * a path in the local file system + * @return the corresponding variable relative path, or null + * if no such path is available + * @since 3.6 + */ + public URI getVariableRelativePathLocation(URI location); + + /** + * Converts the internal format of the linked resource location if the PARENT + * variables is used. For example, if the value is "${PARENT-2-VAR}\foo", the + * converted result is "${VAR}\..\..\foo". + * @param value the value encoded using OS string (as returned from Path.toOSString()) + * @param locationFormat indicates whether the value contains a string that is stored in the linked resource location rather than in the path variable value + * @return the converted path variable value + * @since 3.6 + */ + public String convertToUserEditableFormat(String value, boolean locationFormat); + + /** + * Converts the user editable format to the internal format. + * For example, if the value is "${VAR}\..\..\foo", the + * converted result is "${PARENT-2-VAR}\foo". + * If the string is not directly convertible to a ${PARENT-COUNT-VAR} + * syntax (for example, the editable string "${FOO}bar\..\..\"), intermediate + * path variables will be created. + * @param value the value encoded using OS string (as returned from Path.toOSString()) + * @param locationFormat indicates whether the value contains a string that is stored in the linked resource location rather than in the path variable value + * @return the converted path variable value + * @since 3.6 + */ + public String convertFromUserEditableFormat(String value, boolean locationFormat); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java new file mode 100644 index 0000000000..d36528777c --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java @@ -0,0 +1,1055 @@ +/******************************************************************************* + * 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 - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import java.util.Map; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentTypeMatcher; + +/** + * A project is a type of resource which groups resources + * into buildable, reusable units. + *

      + * Features of projects include: + *

        + *
      • A project collects together a set of files and folders.
      • + *
      • A project's location controls where the project's resources are + * stored in the local file system.
      • + *
      • A project's build spec controls how building is done on the project.
      • + *
      • A project can carry session and persistent properties.
      • + *
      • A project can be open or closed; a closed project is + * passive and has a minimal in-memory footprint.
      • + *
      • A project can have one or more project build configurations.
      • + *
      • A project can carry references to other project build configurations.
      • + *
      • A project can have one or more project natures.
      • + *
      + *

      + *

      + * Projects implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

      + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IProject extends IContainer, IAdaptable { + /** + * Option constant (value 1) indicating that a snapshot to be + * loaded or saved contains a resource tree (refresh information). + * @see #loadSnapshot(int, URI, IProgressMonitor) + * @see #saveSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public static final int SNAPSHOT_TREE = 1; + + /** + * Invokes the build method of the specified builder + * for this project. Does nothing if this project is closed. If this project + * has multiple builders on its build spec matching the given name, only + * the first matching builder will be run. The build is run for the project's + * active build configuration. + *

      + * The builder name is declared in the extension that plugs in + * to the standard org.eclipse.core.resources.builders + * extension point. The arguments are builder specific. + *

      + *

      + * This method may change resources; these changes will be reported + * in a subsequent resource change event. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param kind the kind of build being requested. Valid values are: + *
        + *
      • {@link IncrementalProjectBuilder#FULL_BUILD}- indicates a full build.
      • + *
      • {@link IncrementalProjectBuilder#INCREMENTAL_BUILD}- indicates a incremental build.
      • + *
      • {@link IncrementalProjectBuilder#CLEAN_BUILD}- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states.
      • + *
      + * @param builderName the name of the builder + * @param args a table of builder-specific arguments keyed by argument name + * (key type: String, value type: String); + * null is equivalent to an empty map + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic {@link IResourceStatus#BUILD_FAILED} + * code, but it could also be any other status code; it might + * also be a {@link MultiStatus}. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IProjectDescription + * @see IncrementalProjectBuilder#build(int, Map, IProgressMonitor) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @see IResourceRuleFactory#buildRule() + */ + public void build(int kind, String builderName, Map args, IProgressMonitor monitor) throws CoreException; + + /** + * Builds this project. Does nothing if the project is closed. + *

      + * Building a project involves executing the commands found + * in this project's build spec. The build is run for the project's + * active build configuration. + *

      + *

      + * This method may change resources; these changes will be reported + * in a subsequent resource change event. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param kind the kind of build being requested. Valid values are: + *
        + *
      • IncrementalProjectBuilder.FULL_BUILD - indicates a full build.
      • + *
      • IncrementalProjectBuilder.INCREMENTAL_BUILD - indicates an incremental build.
      • + *
      • CLEAN_BUILD- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states. + *
      + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic BUILD_FAILED + * code, but it could also be any other status code; it might + * also be a multi-status. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IProjectDescription + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IResourceRuleFactory#buildRule() + */ + public void build(int kind, IProgressMonitor monitor) throws CoreException; + + /** + * Builds a specific build configuration of this project. Does nothing if the project is closed + * or the build configuration does not exist. + *

      + * Building a project involves executing the commands found + * in this project's build spec. The build is run for the specific project + * build configuration. + *

      + *

      + * This method may change resources; these changes will be reported + * in a subsequent resource change event. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * @param config build configuration to build + * @param kind the kind of build being requested. Valid values are: + *
        + *
      • IncrementalProjectBuilder.FULL_BUILD - indicates a full build.
      • + *
      • IncrementalProjectBuilder.INCREMENTAL_BUILD - indicates an incremental build.
      • + *
      • CLEAN_BUILD- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states. + *
      + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic BUILD_FAILED + * code, but it could also be any other status code; it might + * also be a multi-status. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IProjectDescription + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IResourceRuleFactory#buildRule() + * @since 3.7 + */ + public void build(IBuildConfiguration config, int kind, IProgressMonitor monitor) throws CoreException; + + /** + * Closes this project. The project need not be open. Closing + * a closed project does nothing. + *

      + * Closing a project involves ensuring that all important project-related + * state is safely stored on disk, and then discarding the in-memory + * representation of its resources and other volatile state, + * including session properties. + * After this method, the project continues to exist in the workspace + * but its member resources (and their members, etc.) do not. + * A closed project can later be re-opened. + *

      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event that includes + * an indication that this project has been closed and its members + * have been removed. + *

      + *

      + * 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 + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #open(IProgressMonitor) + * @see #isOpen() + * @see IResourceRuleFactory#modifyRule(IResource) + */ + public void close(IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new project resource in the workspace using the given project + * description. Upon successful completion, the project will exist but be closed. + *

      + * Newly created projects have no session or persistent properties. + *

      + *

      + * If the project content area given in the project description does not + * contain a project description file, a project description file is written + * in the project content area with the natures, build spec, comment, and + * referenced projects as specified in the given project description. + * If there is an existing project description file, it is not overwritten. + * In either case, this method does not cause natures to be configured. + *

      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project has been added to the workspace. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param description the project description + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project already exists in the workspace.
      • + *
      • The name of this resource is not valid (according to + * IWorkspace.validateName).
      • + *
      • The project location is not valid (according to + * IWorkspace.validateProjectLocation).
      • + *
      • The project description file could not be created in the project + * content area.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#validateProjectLocation(IProject, IPath) + * @see IResourceRuleFactory#createRule(IResource) + */ + public void create(IProjectDescription description, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new project resource in the workspace with files in the default + * location in the local file system. Upon successful completion, the project + * will exist but be closed. + *

      + * Newly created projects have no session or persistent properties. + *

      + *

      + * If the project content area does not contain a project description file, + * an initial project description file is written in the project content area + * with the following information: + *

        + *
      • no references to other projects
      • + *
      • no natures
      • + *
      • an empty build spec
      • + *
      • an empty comment
      • + *
      + * If there is an existing project description file, it is not overwritten. + *

      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this project has been added to the workspace. + *

      + *

      + * 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 + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project already exists in the workspace.
      • + *
      • The name of this resource is not valid (according to + * IWorkspace.validateName).
      • + *
      • The project location is not valid (according to + * IWorkspace.validateProjectLocation).
      • + *
      • The project description file could not be created in the project + * content area.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#validateProjectLocation(IProject, IPath) + * @see IResourceRuleFactory#createRule(IResource) + */ + public void create(IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new project resource in the workspace using the given project + * description. Upon successful completion, the project will exist but be closed. + *

      + * Newly created projects have no session or persistent properties. + *

      + *

      + * If the project content area given in the project description does not + * contain a project description file, a project description file is written + * in the project content area with the natures, build spec, comment, and + * referenced projects as specified in the given project description. + * If there is an existing project description file, it is not overwritten. + * In either case, this method does not cause natures to be configured. + *

      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project has been added to the workspace. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + *

      + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

      + *

      + * Update flags other than those listed above are ignored. + *

      + * + * @param description the project description + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project already exists in the workspace.
      • + *
      • The name of this resource is not valid (according to + * IWorkspace.validateName).
      • + *
      • The project location is not valid (according to + * IWorkspace.validateProjectLocation).
      • + *
      • The project description file could not be created in the project + * content area.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#validateProjectLocation(IProject, IPath) + * @see IResourceRuleFactory#createRule(IResource) + * + * @since 3.4 + */ + public void create(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this project from the workspace. + * No action is taken if this project does not exist. + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   delete(
      +	 *     (deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT )
      +	 *        | (force ? FORCE : IResource.NONE),
      +	 *     monitor);
      +	 * 
      + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param deleteContent a flag controlling how whether content is + * aggressively deleted + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project could not be deleted.
      • + *
      • This project's contents could not be deleted.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int, IProgressMonitor) + * @see #open(IProgressMonitor) + * @see #close(IProgressMonitor) + * @see IResource#delete(int,IProgressMonitor) + * @see IResourceRuleFactory#deleteRule(IResource) + */ + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the active build configuration for the project. + *

      + * If at any point the active configuration is removed from the project, for example + * when updating the list of build configurations, the active build configuration will be set to + * the first build configuration specified by {@link IProjectDescription#setBuildConfigs(String[])}. + *

      + * If all of the build configurations are removed, the active build configuration will be set to the + * default configuration. + *

      + * @return the active build configuration + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist.
      • + *
      • This project is not open.
      • + *
      + * @since 3.7 + */ + public IBuildConfiguration getActiveBuildConfig() throws CoreException; + + /** + * Returns the project {@link IBuildConfiguration} with the given name for this project. + * @param configName the name of the configuration to get + * @return a project configuration + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist.
      • + *
      • This project is not open.
      • + *
      • The configuration does not exist in this project.
      • + *
      + * @see #getBuildConfigs() + * @since 3.7 + */ + public IBuildConfiguration getBuildConfig(String configName) throws CoreException; + + /** + * Returns the build configurations for this project. A project always has at + * least one build configuration, so this will never return an empty list or null. + * The result will not contain duplicates. + * @return a list of project build configurations + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist.
      • + *
      • This project is not open.
      • + *
      + * @since 3.7 + */ + public IBuildConfiguration[] getBuildConfigs() throws CoreException; + + /** + * Returns this project's content type matcher. This content type matcher takes + * project specific preferences and nature-content type associations into + * account. + * + * @return the content type matcher for this project + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist.
      • + *
      • This project is not open.
      • + *
      + * @see IContentTypeMatcher + * @since 3.1 + */ + public IContentTypeMatcher getContentTypeMatcher() throws CoreException; + + /** + * Returns the description for this project. + * The returned value is a copy and cannot be used to modify + * this project. The returned value is suitable for use in creating, + * copying and moving other projects. + * + * @return the description for this project + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist.
      • + *
      • This project is not open.
      • + *
      + * @see #create(IProgressMonitor) + * @see #create(IProjectDescription, IProgressMonitor) + * @see IResource#copy(IProjectDescription, int, IProgressMonitor) + * @see #move(IProjectDescription, boolean, IProgressMonitor) + */ + public IProjectDescription getDescription() throws CoreException; + + /** + * Returns a handle to the file with the given name in this project. + *

      + * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

      + * + * @param name the string name of the member file + * @return the (handle of the) member file + * @see #getFolder(String) + */ + public IFile getFile(String name); + + /** + * Returns a handle to the folder with the given name in this project. + *

      + * This is a resource handle operation; neither the container + * nor the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

      + * + * @param name the string name of the member folder + * @return the (handle of the) member folder + * @see #getFile(String) + */ + public IFolder getFolder(String name); + + /** + * Returns the specified project nature for this project or null if + * the project nature has not been added to this project. + * Clients may downcast to a more concrete type for more nature-specific methods. + * The documentation for a project nature specifies any such additional protocol. + *

      + * This may cause the plug-in that provides the given nature to be activated. + *

      + * + * @param natureId the fully qualified nature extension identifier, formed + * by combining the nature extension id with the id of the declaring plug-in. + * (e.g. "com.example.acmeplugin.coolnature") + * @return the project nature object + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist.
      • + *
      • This project is not open.
      • + *
      • The project nature extension could not be found.
      • + *
      + */ + public IProjectNature getNature(String natureId) throws CoreException; + + /** + * Returns the location in the local file system of the project-specific + * working data area for use by the given plug-in or null + * if the project does not exist. + *

      + * The content, structure, and management of this area is + * the responsibility of the plug-in. This area is deleted when the + * project is deleted. + *

      + * This project needs to exist but does not need to be open. + *

      + * @param plugin the plug-in + * @return a local file system path + * @deprecated Use IProject.getWorkingLocation(plugin.getUniqueIdentifier()). + */ + @Deprecated + public IPath getPluginWorkingLocation(IPluginDescriptor plugin); + + /** + * Returns the location in the local file system of the project-specific + * working data area for use by the bundle/plug-in with the given identifier, + * or null if the project does not exist. + *

      + * The content, structure, and management of this area is + * the responsibility of the bundle/plug-in. This area is deleted when the + * project is deleted. + *

      + * This project needs to exist but does not need to be open. + *

      + * @param id the bundle or plug-in's identifier + * @return a local file system path + * @since 3.0 + */ + public IPath getWorkingLocation(String id); + + /** + * Returns the projects referenced by this project. This includes + * both the static and dynamic references of this project. + * The returned projects need not exist in the workspace. + * The result will not contain duplicates. Returns an empty + * array if there are no referenced projects. + * + * @return a list of projects + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist.
      • + *
      • This project is not open.
      • + *
      + * @see #getReferencedBuildConfigs(String, boolean) + * @see IProjectDescription#getReferencedProjects() + * @see IProjectDescription#getDynamicReferences() + */ + public IProject[] getReferencedProjects() throws CoreException; + + /** + * Returns the list of all open projects which reference + * this project. This project may or may not exist. Returns + * an empty array if there are no referencing projects. + * + * @return a list of open projects referencing this project + */ + public IProject[] getReferencingProjects(); + + /** + * Returns the build configurations referenced by the passed in build configuration + * on this project. + *

      + * This includes both the static and dynamic project level references. These are + * converted to build configurations pointing at the currently active referenced + * project configuration. + * The result will not contain duplicates. + *

      + *

      + * References to active configurations will be translated to references to actual + * build configurations, if the project is accessible. Note that if includeMissing + * is true BuildConfigurations which can't be resolved (i.e. exist on missing projects, + * or aren't listed on the referenced project) are still included in the returned + * IBuildConfiguration array. + *

      + *

      + * Returns an empty array if there are no references. + *

      + * + * @param configName the configuration to get the references for + * @param includeMissing boolean controls whether unresolved buildConfiguration should + * be included in the result + * @return an array of project build configurations + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist.
      • + *
      • This project is not open.
      • + *
      • The build configuration does not exist in this project.
      • + *
      + * @see IProjectDescription#getBuildConfigReferences(String) + * @since 3.7 + */ + public IBuildConfiguration[] getReferencedBuildConfigs(String configName, boolean includeMissing) throws CoreException; + + /** + * Checks whether the project has the specified build configuration. + * + * @param configName the configuration + * @return true if the project has the specified configuration, false otherwise + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist.
      • + *
      • This project is not open.
      • + *
      + * @since 3.7 + */ + public boolean hasBuildConfig(String configName) throws CoreException; + + /** + * Returns whether the project nature specified by the given + * nature extension id has been added to this project. + * + * @param natureId the nature extension identifier + * @return true if the project has the given nature + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist.
      • + *
      • This project is not open.
      • + *
      + */ + public boolean hasNature(String natureId) throws CoreException; + + /** + * Returns true if the project nature specified by the given + * nature extension id is enabled for this project, and false otherwise. + *

      + *

        Reasons for a nature not to be enabled include: + *
      • The nature is not available in the install.
      • + *
      • The nature has not been added to this project.
      • + *
      • The nature has a prerequisite that is not enabled + * for this project.
      • + *
      • The nature specifies "one-of-nature" membership in + * a set, and there is another nature on this project belonging + * to that set.
      • + *
      • The prerequisites for the nature form a cycle.
      • + *
      + *

      + * @param natureId the nature extension identifier + * @return true if the given nature is enabled for this project + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist.
      • + *
      • This project is not open.
      • + *
      + * @since 2.0 + * @see IWorkspace#validateNatureSet(String[]) + */ + public boolean isNatureEnabled(String natureId) throws CoreException; + + /** + * Returns whether this project is open. + *

      + * A project must be opened before it can be manipulated. + * A closed project is passive and has a minimal memory + * footprint; a closed project has no members. + *

      + * + * @return true if this project is open, false if + * this project is closed or does not exist + * @see #open(IProgressMonitor) + * @see #close(IProgressMonitor) + */ + public boolean isOpen(); + + /** + * Loads a snapshot of project meta-data from the given location URI. + * Must be called after the project has been created, but before it is + * opened. The options constant controls what kind of snapshot information + * to load. Valid option values include:
        + *
      • {@link IProject#SNAPSHOT_TREE} - load resource tree (refresh info) + *
      + * + * @param options kind of snapshot information to load + * @param snapshotLocation URI to load from + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • The snapshot was not found at the specified URI.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * @see #saveSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public void loadSnapshot(int options, URI snapshotLocation, + IProgressMonitor monitor) throws CoreException; + + /** + * Renames this project so that it is located at the name in + * the given description. + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   move(description, (force ? FORCE : IResource.NONE), monitor);
      +	 * 
      + *

      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource has been removed from its parent + * and that a corresponding resource has been added to its new parent. + * Additional information provided with resource delta shows that these + * additions and removals are related. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param description the description for the destination project + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
        + *
      • This resource is not accessible.
      • + *
      • This resource or one of its descendents is not local.
      • + *
      • This resource or one of its descendents is out of sync with the local file system + * and force is false.
      • + *
      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + * @see IResource#move(IProjectDescription,int,IProgressMonitor) + * @see IResourceRuleFactory#moveRule(IResource, IResource) + */ + public void move(IProjectDescription description, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Opens this project. No action is taken if the project is already open. + *

      + * Opening a project constructs an in-memory representation + * of its resources from information stored on disk. + *

      + *

      + * When a project is opened for the first time, initial information about the + * project's existing resources can be obtained in the following ways: + *

        + *
      • If a {@link #loadSnapshot(int, URI, IProgressMonitor)} call has been made + * before the open, resources are restored from that file (a file written by + * {@link #saveSnapshot(int, URI, IProgressMonitor)}). When the snapshot includes + * resource tree information and can be loaded without error, no refresh is initiated, + * so the project's resource tree will match what the snapshot provides. + *
      • Otherwise, when the {@link IResource#BACKGROUND_REFRESH} flag is specified, + * resources on disk will be added to the project in the background after + * this method returns. Child resources of the project may not be available + * until this background refresh completes. + *
      • Otherwise, resource information is obtained with a refresh operation in the + * foreground, before this method returns. + *
      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event that includes + * an indication that the project has been opened and its resources + * have been added to the tree. If the BACKGROUND_REFRESH + * update flag is specified, multiple resource change events may occur as + * resources on disk are discovered and added to the tree. + *

      + *

      + * 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 + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #close(IProgressMonitor) + * @see IResource#BACKGROUND_REFRESH + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 3.1 + */ + public void open(int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Opens this project. No action is taken if the project is already open. + *

      + * This is a convenience method, fully equivalent to + * open(IResource.NONE, monitor). + *

      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event that includes + * an indication that the project has been opened and its resources + * have been added to the tree. + *

      + *

      + * 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 + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #close(IProgressMonitor) + * @see IResourceRuleFactory#modifyRule(IResource) + */ + public void open(IProgressMonitor monitor) throws CoreException; + + /** + * Writes a snapshot of project meta-data into the given location URI. + * The options constant controls what kind of snapshot information to + * write. Valid option values include:
        + *
      • {@link IProject#SNAPSHOT_TREE} - save resource tree (refresh info) + *
      + * + * @param options kind of snapshot information to save + * @param snapshotLocation URI for saving the snapshot to + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • The URI is not writable or an error occurs writing the data.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * @see #loadSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public void saveSnapshot(int options, URI snapshotLocation, + IProgressMonitor monitor) throws CoreException; + + /** + * Changes this project resource to match the given project + * description. This project should exist and be open. + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   setDescription(description, KEEP_HISTORY, monitor);
      +	 * 
      + *

      + *

      + * This method requires the {@link IWorkspaceRoot} scheduling rule. + *

      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project's content has changed. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param description the project description + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist in the workspace.
      • + *
      • This project is not open.
      • + *
      • The location in the local file system corresponding to the project + * description file is occupied by a directory.
      • + *
      • The workspace is out of sync with the project description file + * in the local file system .
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      • The file modification validator disallowed the change.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see #getDescription() + * @see IProjectNature#configure() + * @see IProjectNature#deconfigure() + * @see #setDescription(IProjectDescription,int,IProgressMonitor) + */ + public void setDescription(IProjectDescription description, IProgressMonitor monitor) throws CoreException; + + /** + * Changes this project resource to match the given project + * description. This project should exist and be open. + *

      + * The given project description is used to change the project's + * natures, build spec, comment, and referenced projects. + * The name and location of a project cannot be changed using this method; + * these settings in the project description are ignored. To change a project's + * name or location, use {@link IResource#move(IProjectDescription, int, IProgressMonitor)}. + * The project's session and persistent properties are not affected. + *

      + *

      + * If the new description includes nature ids of natures that the project + * did not have before, these natures will be configured in automatically, + * which involves instantiating the project nature and calling + * {@link IProjectNature#configure()} on it. An internal reference to the + * nature object is retained, and will be returned on subsequent calls to + * getNature for the specified nature id. Similarly, any natures + * the project had which are no longer required will be automatically + * de-configured by calling {@link IProjectNature#deconfigure} + * on the nature object and letting go of the internal reference to it. + *

      + *

      + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite the project's description file in the local file system + * provided it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write the project description file in the local file system, overwriting + * any existing one if need be. + *

      + *

      + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of the project description file should be captured in the + * workspace's local history. The local history mechanism serves as a safety net + * to help the user recover from mistakes that might otherwise result in data + * loss. Specifying KEEP_HISTORY is recommended. Note that local + * history is maintained with each individual project, and gets discarded when + * a project is deleted from the workspace. + *

      + *

      + * The AVOID_NATURE_CONFIG update flag controls whether or + * not added and removed natures should be configured or de-configured. If this + * flag is not specified, then added natures will be configured and removed natures + * will be de-configured. If this flag is specified, natures can still be added or + * removed, but they will not be configured or de-configured. + *

      + *

      + * The scheduling rule required for this operation depends on the + * AVOID_NATURE_CONFIG flag. If the flag is specified the + * {@link IResourceRuleFactory#modifyRule} is required; If the flag is not specified, + * the {@link IWorkspaceRoot} scheduling rule is required. + *

      + *

      + * Update flags other than FORCE, KEEP_HISTORY, + * and AVOID_NATURE_CONFIG are ignored. + *

      + *

      + * Prior to modifying the project description file, the file modification + * validator (if provided by the Team plug-in), will be given a chance to + * perform any last minute preparations. Validation is performed by calling + * IFileModificationValidator.validateSave on the project + * description file. If the validation fails, then this operation will fail. + *

      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project's content has changed. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param description the project description + * @param updateFlags bit-wise or of update flag constants + * (FORCE, KEEP_HISTORY and + * AVOID_NATURE_CONFIG) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This project does not exist in the workspace.
      • + *
      • This project is not open.
      • + *
      • The location in the local file system corresponding to the project + * description file is occupied by a directory.
      • + *
      • The workspace is not in sync with the project + * description file in the local file system and FORCE is not + * specified.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      • The file modification validator disallowed the change.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see #getDescription() + * @see IProjectNature#configure() + * @see IProjectNature#deconfigure() + * @see IResource#FORCE + * @see IResource#KEEP_HISTORY + * @see IResource#AVOID_NATURE_CONFIG + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void setDescription(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java new file mode 100644 index 0000000000..17bf5e193d --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java @@ -0,0 +1,385 @@ +/******************************************************************************* + * 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 + * Broadcom Corporation - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A project description contains the meta-data required to define + * a project. In effect, a project description is a project's "content". + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IProjectDescription { + /** + * Constant that denotes the name of the project description file (value + * ".project"). + * The handle of a project's description file is + * project.getFile(DESCRIPTION_FILE_NAME). + * The project description file is located in the root of the project's content area. + * + * @since 2.0 + */ + public static final String DESCRIPTION_FILE_NAME = ".project"; //$NON-NLS-1$ + + /** + * Returns the build configurations referenced by the specified configuration for the + * described project. + *

      + * These references are persisted by the workspace in a private location outside the + * project description file, and as such will not be shared when a project is exported + * or persisted in a repository. As such clients are always + * responsible for setting these references when a project is created or recreated. + *

      + *

      + * The referenced build configurations need not exist in the workspace. + * The result will not contain duplicates. The order of the references is preserved + * from the call to {@link #setBuildConfigReferences(String, IBuildConfiguration[])}. + * Returns an empty array if the provided config doesn't dynamically reference + * any other build configurations, or the given config does not exist in this description. + *

      + * @param configName the configuration in the described project to get the references for + * @return a list of dynamic build configurations + * @see #setBuildConfigReferences(String, IBuildConfiguration[]) + * @since 3.7 + */ + public IBuildConfiguration[] getBuildConfigReferences(String configName); + + /** + * Returns the list of build commands to run when building the described project. + * The commands are listed in the order in which they are to be run. + * + * @return the list of build commands for the described project + */ + public ICommand[] getBuildSpec(); + + /** + * Returns the descriptive comment for the described project. + * + * @return the comment for the described project + */ + public String getComment(); + + /** + * Returns the dynamic project references for the described project. Dynamic + * project references can be used instead of simple project references in cases + * where the reference information is computed dynamically be a third party. + * These references are persisted by the workspace in a private location outside + * the project description file, and as such will not be shared when a project is + * exported or persisted in a repository. A client using project references + * is always responsible for setting these references when a project is created + * or recreated. + *

      + * The returned projects need not exist in the workspace. The result will not + * contain duplicates. Returns an empty array if there are no dynamic project + * references on this description. + * + * @see #getBuildConfigReferences(String) + * @see #getReferencedProjects() + * @see #setDynamicReferences(IProject[]) + * @return a list of projects + * @since 3.0 + */ + public IProject[] getDynamicReferences(); + + /** + * Returns the local file system location for the described project. The path + * will be either an absolute file system path, or a relative path whose first + * segment is the name of a workspace path variable. null is + * returned if the default location should be used. This method will return + * null if this project is not located in the local file system. + * + * @return the location for the described project or null + * @deprecated Since 3.2, project locations are not necessarily in the local file + * system. The more general {@link #getLocationURI()} method should be used instead. + */ + @Deprecated + public IPath getLocation(); + + /** + * Returns the location URI for the described project. null is + * returned if the default location should be used. + * + * @return the location for the described project or null + * @since 3.2 + * @see #setLocationURI(URI) + */ + public URI getLocationURI(); + + /** + * Returns the name of the described project. + * + * @return the name of the described project + */ + public String getName(); + + /** + * Returns the list of natures associated with the described project. + * Returns an empty array if there are no natures on this description. + * + * @return the list of natures for the described project + * @see #setNatureIds(String[]) + */ + public String[] getNatureIds(); + + /** + * Returns the projects referenced by the described project. These references + * are persisted in the project description file (".project") and as such + * will be shared whenever the project is exported to another workspace. For + * references that are likely to change from one workspace to another, dynamic + * references should be used instead. + *

      + * The projects need not exist in the workspace. + * The result will not contain duplicates. Returns an empty + * array if there are no referenced projects on this description. + * + * @see #getDynamicReferences() + * @see #getBuildConfigReferences(String) + * @return a list of projects + */ + public IProject[] getReferencedProjects(); + + /** + * Returns whether the project nature specified by the given + * nature extension id has been added to the described project. + * + * @param natureId the nature extension identifier + * @return true if the described project has the given nature + */ + public boolean hasNature(String natureId); + + /** + * Returns a new build command. + *

      + * Note that the new command does not become part of this project + * description's build spec until it is installed via the setBuildSpec + * method. + *

      + * + * @return a new command + * @see #setBuildSpec(ICommand[]) + */ + public ICommand newCommand(); + + /** + * Sets the active configuration for the described project. + *

      + * If a configuration with the specified name does not exist in the project then the + * first configuration in the project is treated as the active configuration. + *

      + * + * @param configName the configuration to set as the active or default + * @since 3.7 + */ + public void setActiveBuildConfig(String configName); + + /** + * Sets the build configurations for the described project. + *

      + * The passed in names must all be non-null. + * Before they are set, duplicates are removed. + *

      + *

      + * All projects have one default build configuration, and it is impossible to configure + * the project to have no build configurations. + * If the input is null or an empty list, the current configurations are removed, + * and a default build configuration is (re-)added. + *

      + *

      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

      + * + * @param configNames the configurations to set for the described project + * @see IProject#getActiveBuildConfig() + * @see IProject#getBuildConfigs() + * @see IProjectDescription#setActiveBuildConfig(String) + * @since 3.7 + */ + public void setBuildConfigs(String[] configNames); + + /** + * Sets the build configurations referenced by the specified configuration. + *

      + * The configuration to which references are being added needs to exist in this + * description, but the referenced projects and build configurations need not exist. + * A reference with null configuration name is resolved to the active build configuration + * on use. + * Duplicates will be removed. The order of the referenced build configurations is preserved. + * If the given configuration does not exist in this description then this has no effect. + *

      + *

      + * References at the build configuration level take precedence over references at the project level. + *

      + *

      + * Like dynamic references, these build configuration references are persisted as part of workspace + * metadata. + *

      + *

      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

      + * + * @see #getBuildConfigReferences(String) + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @param configName the configuration in the described project to set the references for + * @param references list of build configuration references + * @since 3.7 + */ + public void setBuildConfigReferences(String configName, IBuildConfiguration[] references); + + /** + * Sets the list of build command to run when building the described project. + *

      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

      + * + * @param buildSpec the array of build commands to run + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getBuildSpec() + * @see #newCommand() + */ + public void setBuildSpec(ICommand[] buildSpec); + + /** + * Sets the comment for the described project. + *

      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

      + * + * @param comment the comment for the described project + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getComment() + */ + public void setComment(String comment); + + /** + * Sets the dynamic project references for the described project. + * The projects need not exist in the workspace. Duplicates will be + * removed. + *

      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

      + * @see #getDynamicReferences() + * @see #setBuildConfigReferences(String, IBuildConfiguration[]) + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @param projects list of projects + * @since 3.0 + */ + public void setDynamicReferences(IProject[] projects); + + /** + * Sets the local file system location for the described project. The path must + * be either an absolute file system path, or a relative path whose first + * segment is the name of a defined workspace path variable. If + * null is specified, the default location is used. + *

      + * Setting the location on a description for a project which already + * exists has no effect; the new project location is ignored when the + * description is set on the already existing project. This method is + * intended for use on descriptions for new projects or for destination + * projects for copy and move. + *

      + *

      + * This operation maps the root folder of the project to the exact location + * provided. For example, if the location for project named "P" is set + * to the path c:\my_plugins\Project1, the file resource at workspace path + * /P/index.html would be stored in the local file system at + * c:\my_plugins\Project1\index.html. + *

      + * + * @param location the location for the described project or null + * @see #getLocation() + */ + public void setLocation(IPath location); + + /** + * Sets the location for the described project. + * If null is specified, the default location is used. + *

      + * Setting the location on a description for a project which already + * exists has no effect; the new project location is ignored when the + * description is set on the already existing project. This method is + * intended for use on descriptions for new projects or for destination + * projects for copy and move. + *

      + *

      + * This operation maps the root folder of the project to the exact location + * provided. For example, if the location for project named "P" is set + * to the URI file://c:/my_plugins/Project1, the file resource at workspace path + * /P/index.html would be stored in the local file system at + * file://c:/my_plugins/Project1/index.html. + *

      + * + * @param location the location for the described project or null + * @see #getLocationURI() + * @see IWorkspace#validateProjectLocationURI(IProject, URI) + * @since 3.2 + */ + public void setLocationURI(URI location); + + /** + * Sets the name of the described project. + *

      + * Setting the name on a description and then setting the + * description on the project has no effect; the new name is ignored. + *

      + *

      + * Creating a new project with a description name which doesn't + * match the project handle name results in the description name + * being ignored; the project will be created using the name + * in the handle. + *

      + * + * @param projectName the name of the described project + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getName() + */ + public void setName(String projectName); + + /** + * Sets the list of natures associated with the described project. + * A project created with this description will have these natures + * added to it in the given order. + *

      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

      + * + * @param natures the list of natures + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getNatureIds() + */ + public void setNatureIds(String[] natures); + + /** + * Sets the referenced projects, ignoring any duplicates. + * The order of projects is preserved. + * The projects need not exist in the workspace. + *

      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

      + * + * @param projects a list of projects + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #setBuildConfigReferences(String, IBuildConfiguration[]) + * @see #getReferencedProjects() + */ + public void setReferencedProjects(IProject[] projects); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java new file mode 100644 index 0000000000..1a0b3dfca8 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * Interface for project nature runtime classes. + * It can configure a project with the project nature, or de-configure it. + * When a project is configured with a project nature, this is + * recorded in the list of project natures on the project. + * Individual project natures may expose a more specific runtime type, + * with additional API for manipulating the project in a + * nature-specific way. + *

      + * Clients may implement this interface. + *

      + * + * @see IProject#getNature(String) + * @see IProject#hasNature(String) + * @see IProjectDescription#getNatureIds() + * @see IProjectDescription#hasNature(String) + * @see IProjectDescription#setNatureIds(String[]) + */ +public interface IProjectNature { + /** + * Configures this nature for its project. This is called by the workspace + * when natures are added to the project using IProject.setDescription + * and should not be called directly by clients. The nature extension + * id is added to the list of natures before this method is called, + * and need not be added here. + * + * Exceptions thrown by this method will be propagated back to the caller + * of IProject.setDescription, but the nature will remain in + * the project description. + * + * @exception CoreException if this method fails. + */ + public void configure() throws CoreException; + + /** + * De-configures this nature for its project. This is called by the workspace + * when natures are removed from the project using + * IProject.setDescription and should not be called directly by + * clients. The nature extension id is removed from the list of natures before + * this method is called, and need not be removed here. + * + * Exceptions thrown by this method will be propagated back to the caller + * of IProject.setDescription, but the nature will still be + * removed from the project description. + * * + * @exception CoreException if this method fails. + */ + public void deconfigure() throws CoreException; + + /** + * Returns the project to which this project nature applies. + * + * @return the project handle + */ + public IProject getProject(); + + /** + * Sets the project to which this nature applies. + * Used when instantiating this project nature runtime. + * This is called by IProject.create() or + * IProject.setDescription() + * and should not be called directly by clients. + * + * @param project the project to which this nature applies + */ + public void setProject(IProject project); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java new file mode 100644 index 0000000000..a7f7bf061d --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +/** + * A project nature descriptor contains information about a project nature + * obtained from the plug-in manifest (plugin.xml) file. + *

      + * Nature descriptors are platform-defined objects that exist + * independent of whether that nature's plug-in has been started. + * In contrast, a project nature's runtime object (IProjectNature) + * generally runs plug-in-defined code. + *

      + * + * @see IProjectNature + * @see IWorkspace#getNatureDescriptor(String) + * @since 2.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 IProjectNatureDescriptor { + /** + * Returns the unique identifier of this nature. + *

      + * The nature identifier is composed of the nature's plug-in id and the simple + * id of the nature extension. For example, if plug-in "com.xyz" + * defines a nature extension with id "myNature", the unique + * nature identifier will be "com.xyz.myNature". + *

      + * @return the unique nature identifier + */ + public String getNatureId(); + + /** + * Returns a displayable label for this nature. + * Returns the empty string if no label for this nature + * is specified in the plug-in manifest file. + *

      Note that any translation specified in the plug-in manifest + * file is automatically applied. + *

      + * + * @return a displayable string label for this nature, + * possibly the empty string + */ + public String getLabel(); + + /** + * Returns the unique identifiers of the natures required by this nature. + * Nature requirements are specified by the "requires-nature" + * element on a nature extension. + * Returns an empty array if no natures are required by this nature. + * + * @return an array of nature ids that this nature requires, + * possibly an empty array. + */ + public String[] getRequiredNatureIds(); + + /** + * Returns the identifiers of the nature sets that this nature belongs to. + * Nature set inclusion is specified by the "one-of-nature" + * element on a nature extension. + * Returns an empty array if no nature sets are specified for this nature. + * + * @return an array of nature set ids that this nature belongs to, + * possibly an empty array. + */ + public String[] getNatureSetIds(); + + /** + * Returns whether this project nature allows linked resources to be created + * in projects where this nature is installed. + * + * @return boolean true if creating links is allowed, + * and false otherwise. + * @see IFolder#createLink(org.eclipse.core.runtime.IPath, int, org.eclipse.core.runtime.IProgressMonitor) + * @see IFile#createLink(org.eclipse.core.runtime.IPath, int, org.eclipse.core.runtime.IProgressMonitor) + * @since 2.1 + */ + public boolean isLinkingAllowed(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java new file mode 100644 index 0000000000..083e828e6f --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java @@ -0,0 +1,2805 @@ +/******************************************************************************* + * 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 + * Red Hat Incorporated - get/setResourceAttribute code + * Oakland Software Incorporated - added getSessionProperties and getPersistentProperties + * Serge Beauchamp (Freescale Semiconductor) - [252996] add hasFilters() + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import java.util.Map; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * The workspace analog of file system files + * and directories. There are exactly four types of resource: + * files, folders, projects and the workspace root. + *

      + * File resources are similar to files in that they + * hold data directly. Folder resources are analogous to directories in that they + * hold other resources but cannot directly hold data. Project resources + * group files and folders into reusable clusters. The workspace root is the + * top level resource under which all others reside. + *

      + *

      + * Features of resources: + *

        + *
      • IResource objects are handles to state maintained + * by a workspace. That is, resource objects do not actually contain data + * themselves but rather represent resource state and give it behavior. Programmers + * are free to manipulate handles for resources that do not exist in a workspace + * but must keep in mind that some methods and operations require that an actual + * resource be available.
      • + *
      • Resources have two different kinds of properties as detailed below. All + * properties are keyed by qualified names.
      • + *
          + *
        • Session properties: Session properties live for the lifetime of one execution of + * the workspace. They are not stored on disk. They can carry arbitrary + * object values. Clients should be aware that these values are kept in memory + * at all times and, as such, the values should not be large.
        • + *
        • Persistent properties: Persistent properties have string values which are stored + * on disk across platform sessions. The value of a persistent property is a + * string which should be short (i.e., under 2KB).
        • + *
        + * + *
      • Resources are identified by type and by their path, which is similar to a file system + * path. The name of a resource is the last segment of its path. A resource's parent + * is located by removing the last segment (the resource's name) from the resource's full path.
      • + *
      • Resources can be local or non-local. A non-local resource is one whose + * contents and properties have not been fetched from a repository.
      • + *
      • Phantom resources represent incoming additions or outgoing deletions + * which have yet to be reconciled with a synchronization partner.
      • + *
      + *

      + *

      + * Resources implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

      + * + * @see IWorkspace + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResource extends IAdaptable, ISchedulingRule { + + /*==================================================================== + * Constants defining resource types: There are four possible resource types + * and their type constants are in the integer range 1 to 8 as defined below. + *====================================================================*/ + + /** + * Type constant (bit mask value 1) which identifies file resources. + * + * @see IResource#getType() + * @see IFile + */ + public static final int FILE = 0x1; + + /** + * Type constant (bit mask value 2) which identifies folder resources. + * + * @see IResource#getType() + * @see IFolder + */ + public static final int FOLDER = 0x2; + + /** + * Type constant (bit mask value 4) which identifies project resources. + * + * @see IResource#getType() + * @see IProject + */ + public static final int PROJECT = 0x4; + + /** + * Type constant (bit mask value 8) which identifies the root resource. + * + * @see IResource#getType() + * @see IWorkspaceRoot + */ + public static final int ROOT = 0x8; + + /*==================================================================== + * Constants defining the depth of resource tree traversal: + *====================================================================*/ + + /** + * Depth constant (value 0) indicating this resource, but not any of its members. + */ + public static final int DEPTH_ZERO = 0; + + /** + * Depth constant (value 1) indicating this resource and its direct members. + */ + public static final int DEPTH_ONE = 1; + + /** + * Depth constant (value 2) indicating this resource and its direct and + * indirect members at any depth. + */ + public static final int DEPTH_INFINITE = 2; + + /*==================================================================== + * Constants for update flags for delete, move, copy, open, etc.: + *====================================================================*/ + + /** + * Update flag constant (bit mask value 1) indicating that the operation + * should proceed even if the resource is out of sync with the local file + * system. + * + * @since 2.0 + */ + public static final int FORCE = 0x1; + + /** + * Update flag constant (bit mask value 2) indicating that the operation + * should maintain local history by taking snapshots of the contents of + * files just before being overwritten or deleted. + * + * @see IFile#getHistory(IProgressMonitor) + * @since 2.0 + */ + public static final int KEEP_HISTORY = 0x2; + + /** + * Update flag constant (bit mask value 4) indicating that the operation + * should delete the files and folders of a project. + *

      + * Deleting a project that is open ordinarily deletes all its files and folders, + * whereas deleting a project that is closed retains its files and folders. + * Specifying ALWAYS_DELETE_PROJECT_CONTENT indicates that the contents + * of a project are to be deleted regardless of whether the project is open or closed + * at the time; specifying NEVER_DELETE_PROJECT_CONTENT indicates that + * the contents of a project are to be retained regardless of whether the project + * is open or closed at the time. + *

      + * + * @see #NEVER_DELETE_PROJECT_CONTENT + * @since 2.0 + */ + public static final int ALWAYS_DELETE_PROJECT_CONTENT = 0x4; + + /** + * Update flag constant (bit mask value 8) indicating that the operation + * should preserve the files and folders of a project. + *

      + * Deleting a project that is open ordinarily deletes all its files and folders, + * whereas deleting a project that is closed retains its files and folders. + * Specifying ALWAYS_DELETE_PROJECT_CONTENT indicates that the contents + * of a project are to be deleted regardless of whether the project is open or closed + * at the time; specifying NEVER_DELETE_PROJECT_CONTENT indicates that + * the contents of a project are to be retained regardless of whether the project + * is open or closed at the time. + *

      + * + * @see #ALWAYS_DELETE_PROJECT_CONTENT + * @since 2.0 + */ + public static final int NEVER_DELETE_PROJECT_CONTENT = 0x8; + + /** + * Update flag constant (bit mask value 16) indicating that the link creation + * should proceed even if the local file system file or directory is missing. + * + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @since 2.1 + */ + public static final int ALLOW_MISSING_LOCAL = 0x10; + + /** + * Update flag constant (bit mask value 32) indicating that a copy or move + * operation should only copy the link, rather than copy the underlying + * contents of the linked resource. + * + * @see #copy(IPath, int, IProgressMonitor) + * @see #move(IPath, int, IProgressMonitor) + * @since 2.1 + */ + public static final int SHALLOW = 0x20; + + /** + * Update flag constant (bit mask value 64) indicating that setting the + * project description should not attempt to configure and de-configure + * natures. + * + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @since 3.0 + */ + public static final int AVOID_NATURE_CONFIG = 0x40; + + /** + * Update flag constant (bit mask value 128) indicating that opening a + * project for the first time or creating a linked folder should refresh in the + * background. + * + * @see IProject#open(int, IProgressMonitor) + * @see IFolder#createLink(URI, int, IProgressMonitor) + * @since 3.1 + */ + public static final int BACKGROUND_REFRESH = 0x80; + + /** + * Update flag constant (bit mask value 256) indicating that a + * resource should be replaced with a resource of the same name + * at a different file system location. + * + * @see IFile#createLink(URI, int, IProgressMonitor) + * @see IFolder#createLink(URI, int, IProgressMonitor) + * @see IResource#move(IProjectDescription, int, IProgressMonitor) + * @since 3.2 + */ + public static final int REPLACE = 0x100; + + /** + * Update flag constant (bit mask value 512) indicating that ancestor + * resources of the target resource should be checked. + * + * @see IResource#isLinked(int) + * @since 3.2 + */ + public static final int CHECK_ANCESTORS = 0x200; + + /** + * Update flag constant (bit mask value 0x400) indicating that a + * resource should be marked as derived. + * + * @see IFile#create(java.io.InputStream, int, IProgressMonitor) + * @see IFolder#create(int, boolean, IProgressMonitor) + * @see IResource#setDerived(boolean) + * @since 3.2 + */ + public static final int DERIVED = 0x400; + + /** + * Update flag constant (bit mask value 0x800) indicating that a + * resource should be marked as team private. + * + * @see IFile#create(java.io.InputStream, int, IProgressMonitor) + * @see IFolder#create(int, boolean, IProgressMonitor) + * @see IResource#copy(IPath, int, IProgressMonitor) + * @see IResource#setTeamPrivateMember(boolean) + * @since 3.2 + */ + public static final int TEAM_PRIVATE = 0x800; + + /** + * Update flag constant (bit mask value 0x1000) indicating that a + * resource should be marked as a hidden resource. + * + * @since 3.4 + */ + public static final int HIDDEN = 0x1000; + + /** + * Update flag constant (bit mask value 0x2000) indicating that a + * resource should be marked as a virtual resource. + * + * @see IFolder#create(int, boolean, IProgressMonitor) + * @since 3.6 + */ + public static final int VIRTUAL = 0x2000; + + /*==================================================================== + * Other constants: + *====================================================================*/ + + /** + * Modification stamp constant (value -1) indicating no modification stamp is + * available. + * + * @see #getModificationStamp() + */ + public static final int NULL_STAMP = -1; + + /** + * General purpose zero-valued bit mask constant. Useful whenever you need to + * supply a bit mask with no bits set. + *

      + * Example usage: + * + *

      +	 *    delete(IResource.NONE, null)
      +	 * 
      + * + *

      + * + * @since 2.0 + */ + public static final int NONE = 0; + + /** + * Accepts the given visitor for an optimized traversal. + * The visitor's visit method is called, and is provided with a + * proxy to this resource. The proxy is a transient object that can be queried + * very quickly for information about the resource. If the actual resource + * handle is needed, it can be obtained from the proxy. Requesting the resource + * handle, or the full path of the resource, will degrade performance of the + * visit. + *

      + * The entire subtree under the given resource is traversed to infinite depth, + * unless the visitor ignores a subtree by returning false from its + * visit method. + *

      + *

      + * This is a convenience method, fully equivalent to + * accept(visitor, IResource.DEPTH_INFINITE, memberFlags). + *

      + *

      No guarantees are made about the behavior of this method if resources + * are deleted or added during the traversal of this resource hierarchy. If + * resources are deleted during the traversal, they may still be passed to the + * visitor; if resources are created, they may not be passed to the visitor. If + * resources other than the one being visited are modified during the traversal, + * the resource proxy may contain stale information when that resource is + * visited. + *

      +

      + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified in the member + * flags (recommended), only member resources that exist will be visited. + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is specified, the visit will + * also include any phantom member resource that the workspace is keeping track of. + *

      + *

      + * If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not specified + * (recommended), team private members will not be visited. If the + * {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the member + * flags, team private member resources are visited as well. + *

      + *

      + * If the {@link IContainer#INCLUDE_HIDDEN} flag is not specified (recommended), + * hidden resources will not be visited. If the {@link IContainer#INCLUDE_HIDDEN} flag is specified + * in the member flags, hidden resources are visited as well. + *

      + *

      + * If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified (recommended), + * the resource is checked for existence before the visitor's visit + * method is called. If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is specified + * in the member flags, the resource is not checked for existence before the visitor's + * visit method is called. Children of the resource are never checked + * for existence. + *

      + * + * @param visitor the visitor + * @param memberFlags bit-wise or of member flag constants + * ({@link IContainer#INCLUDE_PHANTOMS}, {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} + * and {@link IContainer#INCLUDE_HIDDEN}) indicating which members are of interest + * and {@link IContainer#DO_NOT_CHECK_EXISTENCE} if the resource on which the method is + * called should not be checked for existence + * @exception CoreException if this request fails. Reasons include: + *
        + *
      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource does not exist.
      • + *
      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource is a project that is not open.
      • + *
      • the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified and + * this resource does not exist.
      • + *
      • The visitor failed with this exception.
      • + *
      + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IContainer#DO_NOT_CHECK_EXISTENCE + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResourceProxyVisitor#visit(IResourceProxy) + * @since 2.1 + */ + public void accept(IResourceProxyVisitor visitor, int memberFlags) throws CoreException; + + /** + * Accepts the given visitor for an optimized traversal. + * The visitor's visit method is called, and is provided with a + * proxy to this resource. The proxy is a transient object that can be queried + * very quickly for information about the resource. If the actual resource + * handle is needed, it can be obtained from the proxy. Requesting the resource + * handle, or the full path of the resource, will degrade performance of the + * visit. + *

      + * The entire subtree under the given resource is traversed to the supplied depth, + * unless the visitor ignores a subtree by returning false from its + * visit method. + *

      + *

      No guarantees are made about the behavior of this method if resources + * are deleted or added during the traversal of this resource hierarchy. If + * resources are deleted during the traversal, they may still be passed to the + * visitor; if resources are created, they may not be passed to the visitor. If + * resources other than the one being visited are modified during the traversal, + * the resource proxy may contain stale information when that resource is + * visited. + *

      + *

      + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified in the member + * flags (recommended), only member resources that exist will be visited. + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is specified, the visit will + * also include any phantom member resource that the workspace is keeping track of. + *

      + *

      + * If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not specified + * (recommended), team private members will not be visited. If the + * {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the member + * flags, team private member resources are visited as well. + *

      + *

      + * If the {@link IContainer#INCLUDE_HIDDEN} flag is not specified (recommended), + * hidden resources will not be visited. If the {@link IContainer#INCLUDE_HIDDEN} flag is specified + * in the member flags, hidden resources are visited as well. + *

      + *

      + * If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified (recommended), + * the resource is checked for existence before the visitor's visit + * method is called. If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is specified + * in the member flags, the resource is not checked for existence before the visitor's + * visit method is called. Children of the resource are never checked + * for existence. + *

      + * + * @param visitor the visitor + * @param depth the depth to which members of this resource should be + * visited. One of {@link IResource#DEPTH_ZERO}, {@link IResource#DEPTH_ONE}, + * or {@link IResource#DEPTH_INFINITE}. + * @param memberFlags bit-wise or of member flag constants + * ({@link IContainer#INCLUDE_PHANTOMS}, {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} + * and {@link IContainer#INCLUDE_HIDDEN}) indicating which members are of interest + * and {@link IContainer#DO_NOT_CHECK_EXISTENCE} if the resource on which the method is + * called should not be checked for existence + * @exception CoreException if this request fails. Reasons include: + *
        + *
      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource does not exist.
      • + *
      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource is a project that is not open.
      • + *
      • the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified and + * this resource does not exist.
      • + *
      • The visitor failed with this exception.
      • + *
      + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IContainer#DO_NOT_CHECK_EXISTENCE + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceProxyVisitor#visit(IResourceProxy) + * @since 3.8 + */ + public void accept(IResourceProxyVisitor visitor, int depth, int memberFlags) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource. If the visitor returns true, this method + * visits this resource's members. + *

      + * This is a convenience method, fully equivalent to + * accept(visitor, IResource.DEPTH_INFINITE, IResource.NONE). + *

      + * + * @param visitor the visitor + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • The visitor failed with this exception.
      • + *
      + * @see IResourceVisitor#visit(IResource) + * @see #accept(IResourceVisitor,int,int) + */ + public void accept(IResourceVisitor visitor) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource. If the visitor returns false, + * this resource's members are not visited. + *

      + * The subtree under the given resource is traversed to the supplied depth. + *

      + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   accept(visitor, depth, includePhantoms ? IContainer.INCLUDE_PHANTOMS : IResource.NONE);
      +	 * 
      + *

      + * + * @param visitor the visitor + * @param depth the depth to which members of this resource should be + * visited. One of {@link IResource#DEPTH_ZERO}, {@link IResource#DEPTH_ONE}, + * or {@link IResource#DEPTH_INFINITE}. + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest. + * @exception CoreException if this request fails. Reasons include: + *
        + *
      • includePhantoms is false and + * this resource does not exist.
      • + *
      • includePhantoms is true and + * this resource does not exist and is not a phantom.
      • + *
      • The visitor failed with this exception.
      • + *
      + * @see IResource#isPhantom() + * @see IResourceVisitor#visit(IResource) + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResource#accept(IResourceVisitor,int,int) + */ + public void accept(IResourceVisitor visitor, int depth, boolean includePhantoms) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource. If the visitor returns false, + * this resource's members are not visited. + *

      + * The subtree under the given resource is traversed to the supplied depth. + *

      + *

      + * No guarantees are made about the behavior of this method if resources are + * deleted or added during the traversal of this resource hierarchy. If + * resources are deleted during the traversal, they may still be passed to the + * visitor; if resources are created, they may not be passed to the visitor. + *

      + *

      + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified in the member + * flags (recommended), only member resources that exists are visited. + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is specified, the visit also + * includes any phantom member resource that the workspace is keeping track of. + *

      + *

      + * If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not specified + * (recommended), team private members are not visited. If the + * {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the member + * flags, team private member resources are visited as well. + *

      + *

      + * If the {@link IContainer#EXCLUDE_DERIVED} flag is not specified + * (recommended), derived resources are visited. If the + * {@link IContainer#EXCLUDE_DERIVED} flag is specified in the member + * flags, derived resources are not visited. + *

      + *

      + * If the {@link IContainer#INCLUDE_HIDDEN} flag is not specified (recommended), + * hidden resources will not be visited. If the {@link IContainer#INCLUDE_HIDDEN} flag is specified + * in the member flags, hidden resources are visited as well. + *

      + *

      + * If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified (recommended), + * the resource is checked for existence before the visitor's visit + * method is called. If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is specified + * in the member flags, the resource is not checked for existence before the visitor's + * visit method is called. Children of the resource are never checked + * for existence. + *

      + * + * @param visitor the visitor + * @param depth the depth to which members of this resource should be + * visited. One of {@link IResource#DEPTH_ZERO}, {@link IResource#DEPTH_ONE}, + * or {@link IResource#DEPTH_INFINITE}. + * @param memberFlags bit-wise or of member flag constants + * ({@link IContainer#INCLUDE_PHANTOMS}, {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS}, + * {@link IContainer#INCLUDE_HIDDEN} and {@link IContainer#EXCLUDE_DERIVED}) indicating + * which members are of interest and {@link IContainer#DO_NOT_CHECK_EXISTENCE} + * if the resource on which the method is called should not be checked for existence + * @exception CoreException if this request fails. Reasons include: + *
        + *
      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource does not exist.
      • + *
      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource is a project that is not open.
      • + *
      • the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified and + * this resource does not exist.
      • + *
      • The visitor failed with this exception.
      • + *
      + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IContainer#EXCLUDE_DERIVED + * @see IContainer#DO_NOT_CHECK_EXISTENCE + * @see IResource#isDerived() + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResource#isHidden() + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceVisitor#visit(IResource) + * @since 2.0 + */ + public void accept(IResourceVisitor visitor, int depth, int memberFlags) throws CoreException; + + /** + * Removes the local history of this resource and its descendents. + *

      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + */ + public void clearHistory(IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this resource at the given path. + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   copy(destination, (force ? FORCE : IResource.NONE), monitor);
      +	 * 
      + *

      + *

      + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource copy has been added to its new parent. + *

      + *

      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource or one of its descendents is not local.
      • + *
      • The source or destination is the workspace root.
      • + *
      • The source is a project but the destination is not.
      • + *
      • The destination is a project but the source is not.
      • + *
      • The resource corresponding to the parent destination path does not exist.
      • + *
      • The resource corresponding to the parent destination path is a closed project.
      • + *
      • A resource at destination path does exist.
      • + *
      • This resource or one of its descendents is out of sync with the local file + * system and force is false.
      • + *
      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
      • + *
      • The source resource is a file and the destination path specifies a project.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + */ + public void copy(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this resource at the given path. The resource's + * descendents are copied as well. The path of this resource must not be a + * prefix of the destination path. The workspace root may not be the source or + * destination location of a copy operation, and a project can only be copied to + * another project. After successful completion, corresponding new resources + * will exist at the given path; their contents and properties will be copies of + * the originals. The original resources are not affected. + *

      + * The supplied path may be absolute or relative. Absolute paths fully specify + * the new location for the resource, including its project. Relative paths are + * considered to be relative to the container of the resource being copied. A + * trailing separator is ignored. + *

      + *

      + * Calling this method with a one segment absolute destination path is + * equivalent to calling: + *

      +	 *   copy(workspace.newProjectDescription(folder.getName()),updateFlags,monitor);
      +	 * 
      + *

      + *

      When a resource is copied, its persistent properties are copied with it. + * Session properties and markers are not copied. + *

      + *

      + * The FORCE update flag controls how this method deals with cases + * where the workspace is not completely in sync with the local file system. If + * FORCE is not specified, the method will only attempt to copy + * resources that are in sync with the corresponding files and directories in + * the local file system; it will fail if it encounters a resource that is out + * of sync with the file system. However, if FORCE is specified, + * the method copies all corresponding files and directories from the local file + * system, including ones that have been recently updated or created. Note that + * in both settings of the FORCE flag, the operation fails if the + * newly created resources in the workspace would be out of sync with the local + * file system; this ensures files in the file system cannot be accidentally + * overwritten. + *

      + *

      + * The SHALLOW update flag controls how this method deals with linked + * resources. If SHALLOW is not specified, then the underlying + * contents of the linked resource will always be copied in the file system. In + * this case, the destination of the copy will never be a linked resource or + * contain any linked resources. If SHALLOW is specified when a + * linked resource is copied into another project, a new linked resource is + * created in the destination project that points to the same file system + * location. When a project containing linked resources is copied, the new + * project will contain the same linked resources pointing to the same file + * system locations. For both of these shallow cases, no files on disk under + * the linked resource are actually copied. With the SHALLOW flag, + * copying of linked resources into anything other than a project is not + * permitted. The SHALLOW update flag is ignored when copying non- + * linked resources. + *

      + *

      + * The {@link #DERIVED} update flag indicates that the new resource + * should immediately be set as a derived resource. Specifying this flag + * is equivalent to atomically calling {@link #setDerived(boolean)} + * with a value of true immediately after creating the resource. + *

      + *

      + * The {@link #TEAM_PRIVATE} update flag indicates that the new resource + * should immediately be set as a team private resource. Specifying this flag + * is equivalent to atomically calling {@link #setTeamPrivateMember(boolean)} + * with a value of true immediately after creating the resource. + *

      + *

      + * The {@link #HIDDEN} update flag indicates that the new resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link #setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

      + *

      + * Update flags other than those listed above are ignored. + *

      + *

      + * This operation changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resource copy has been added to its new parent. + *

      + *

      + * An attempt will be made to copy the local history for this resource and its children, + * to the destination. Since local history existence is a safety-net mechanism, failure + * of this action will not result in automatic failure of the copy operation. + *

      + *

      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param destination the destination path + * @param updateFlags bit-wise or of update flag constants + * ({@link #FORCE}, {@link #SHALLOW}, {@link #DERIVED}, {@link #TEAM_PRIVATE}, {@link #HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource or one of its descendents is not local.
      • + *
      • The source or destination is the workspace root.
      • + *
      • The source is a project but the destination is not.
      • + *
      • The destination is a project but the source is not.
      • + *
      • The resource corresponding to the parent destination path does not exist.
      • + *
      • The resource corresponding to the parent destination path is a closed project.
      • + *
      • The source is a linked resource, but the destination is not a project, + * and SHALLOW is specified.
      • + *
      • A resource at destination path does exist.
      • + *
      • This resource or one of its descendants is out of sync with the local file + * system and FORCE is not specified.
      • + *
      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendants.
      • + *
      • The source resource is a file and the destination path specifies a project.
      • + *
      • The source is a linked resource, and the destination path does not + * specify a project.
      • + *
      • The location of the source resource on disk is the same or a prefix of + * the location of the destination resource on disk.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see #FORCE + * @see #SHALLOW + * @see #DERIVED + * @see #TEAM_PRIVATE + * @see IResourceRuleFactory#copyRule(IResource, IResource) + * @since 2.0 + */ + public void copy(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this project using the given project description. + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   copy(description, (force ? FORCE : IResource.NONE), monitor);
      +	 * 
      + *

      + *

      + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource copy has been added to its new parent. + *

      + *

      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param description the destination project description + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource or one of its descendents is not local.
      • + *
      • This resource is not a project.
      • + *
      • The project described by the given description already exists.
      • + *
      • This resource or one of its descendents is out of sync with the local file + * system and force is false.
      • + *
      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + */ + public void copy(IProjectDescription description, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this project using the given project description. + * The project's descendents are copied as well. The description specifies the + * name, location and attributes of the new project. After successful + * completion, corresponding new resources will exist at the given path; their + * contents and properties will be copies of the originals. The original + * resources are not affected. + *

      + * When a resource is copied, its persistent properties are copied with it. + * Session properties and markers are not copied. + *

      + *

      The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to copy resources that are in sync with the corresponding files and + * directories in the local file system; it will fail if it encounters a + * resource that is out of sync with the file system. However, if + * FORCE is specified, the method copies all corresponding files + * and directories from the local file system, including ones that have been + * recently updated or created. Note that in both settings of the + * FORCE flag, the operation fails if the newly created resources + * in the workspace would be out of sync with the local file system; this + * ensures files in the file system cannot be accidentally overwritten. + *

      + *

      + * The SHALLOW update flag controls how this method deals with + * linked resources. If SHALLOW is not specified, then the + * underlying contents of any linked resources in the project will always be + * copied in the file system. In this case, the destination of the copy will + * never contain any linked resources. If SHALLOW is specified + * when a project containing linked resources is copied, new linked resources + * are created in the destination project that point to the same file system + * locations. In this case, no files on disk under linked resources are + * actually copied. The SHALLOW update flag is ignored when copying + * non- linked resources. + *

      + *

      + * Update flags other than FORCE or SHALLOW are ignored. + *

      + *

      + * An attempt will be made to copy the local history for this resource and its children, + * to the destination. Since local history existence is a safety-net mechanism, failure + * of this action will not result in automatic failure of the copy operation. + *

      + *

      This operation changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resource copy has been added to its new parent. + *

      + *

      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param description the destination project description + * @param updateFlags bit-wise or of update flag constants + * (FORCE and SHALLOW) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource or one of its descendents is not local.
      • + *
      • This resource is not a project.
      • + *
      • The project described by the given description already exists.
      • + *
      • This resource or one of its descendents is out of sync with the local file + * system and FORCE is not specified.
      • + *
      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #FORCE + * @see #SHALLOW + * @see IResourceRuleFactory#copyRule(IResource, IResource) + * @since 2.0 + */ + public void copy(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates and returns the marker with the specified type on this resource. + * Marker type ids should be the id of an extension installed in the + * org.eclipse.core.resources.markers extension + * point. The specified type string must not be null. + * + * @param type the type of the marker to create + * @return the handle of the new marker + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is a project that is not open.
      • + *
      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public IMarker createMarker(String type) throws CoreException; + + /** + * Creates a resource proxy representing the current state of this resource. + *

      + * Note that once a proxy has been created, it does not stay in sync + * with the corresponding resource. Changes to the resource after + * the proxy is created will not be reflected in the state of the proxy. + *

      + * + * @return A proxy representing this resource + * @since 3.2 + */ + public IResourceProxy createProxy(); + + /** + * Deletes this resource from the workspace. + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   delete(force ? FORCE : IResource.NONE, monitor);
      +	 * 
      + *

      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource could not be deleted for some reason.
      • + *
      • This resource or one of its descendents is out of sync with the local file system + * and force is false.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IResource#delete(int,IProgressMonitor) + */ + public void delete(boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this resource from the workspace. + * Deletion applies recursively to all members of this resource in a "best- + * effort" fashion. That is, all resources which can be deleted are deleted. + * Resources which could not be deleted are noted in a thrown exception. The + * method does not fail if resources do not exist; it fails only if resources + * could not be deleted. + *

      + * Deleting a non-linked resource also deletes its contents from the local file + * system. In the case of a file or folder resource, the corresponding file or + * directory in the local file system is deleted. Deleting an open project + * recursively deletes its members; deleting a closed project just gets rid of + * the project itself (closed projects have no members); files in the project's + * local content area are retained; referenced projects are unaffected. + *

      + *

      + * Deleting a linked resource does not delete its contents from the file system, + * it just removes that resource and its children from the workspace. Deleting + * children of linked resources does remove the contents from the file system. + *

      + *

      + * Deleting a resource also deletes its session and persistent properties and + * markers. + *

      + *

      + * Deleting a non-project resource which has sync information converts the + * resource to a phantom and retains the sync information for future use. + *

      + *

      + * Deleting the workspace root resource recursively deletes all projects, + * and removes all markers, properties, sync info and other data related to the + * workspace root; the root resource itself is not deleted, however. + *

      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + *

      + * The {@link #FORCE} update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local + * file system. If {@link #FORCE} is not specified, the method will only + * attempt to delete files and directories in the local file system that + * correspond to, and are in sync with, resources in the workspace; it will fail + * if it encounters a file or directory in the file system that is out of sync + * with the workspace. This option ensures there is no unintended data loss; + * it is the recommended setting. However, if {@link #FORCE} is specified, + * the method will ruthlessly attempt to delete corresponding files and + * directories in the local file system, including ones that have been recently + * updated or created. + *

      + *

      + * The {@link #KEEP_HISTORY} update flag controls whether or not files that + * are about to be deleted from the local file system have their current + * contents saved in the workspace's local history. The local history mechanism + * serves as a safety net to help the user recover from mistakes that might + * otherwise result in data loss. Specifying {@link #KEEP_HISTORY} is + * recommended except in circumstances where past states of the files are of no + * conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. Hence {@link #KEEP_HISTORY} is only really applicable + * when deleting files and folders, but not projects. + *

      + *

      + * The {@link #ALWAYS_DELETE_PROJECT_CONTENT} update flag controls how + * project deletions are handled. If {@link #ALWAYS_DELETE_PROJECT_CONTENT} + * is specified, then the files and folders in a project's local content area + * are deleted, regardless of whether the project is open or closed; + * {@link #FORCE} is assumed regardless of whether it is specified. If + * {@link #NEVER_DELETE_PROJECT_CONTENT} is specified, then the files and + * folders in a project's local content area are retained, regardless of whether + * the project is open or closed; the {@link #FORCE} flag is ignored. If + * neither of these flags is specified, files and folders in a project's local + * content area from open projects (subject to the {@link #FORCE} flag), but + * never from closed projects. + *

      + * + * @param updateFlags bit-wise or of update flag constants ( + * {@link #FORCE}, {@link #KEEP_HISTORY}, + * {@link #ALWAYS_DELETE_PROJECT_CONTENT}, + * and {@link #NEVER_DELETE_PROJECT_CONTENT}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource could not be deleted for some reason.
      • + *
      • This resource or one of its descendents is out of sync with the local file system + * and {@link #FORCE} is not specified.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IFile#delete(boolean, boolean, IProgressMonitor) + * @see IFolder#delete(boolean, boolean, IProgressMonitor) + * @see #FORCE + * @see #KEEP_HISTORY + * @see #ALWAYS_DELETE_PROJECT_CONTENT + * @see #NEVER_DELETE_PROJECT_CONTENT + * @see IResourceRuleFactory#deleteRule(IResource) + * @since 2.0 + */ + public void delete(int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes all markers on this resource of the given type, and, + * optionally, deletes such markers from its children. If includeSubtypes + * is false, only markers whose type exactly matches + * the given type are deleted. + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

      + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param depth how far to recurse (see IResource.DEPTH_* ) + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is a project that is not open.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException; + + /** + * Compares two objects for equality; + * for resources, equality is defined in terms of their handles: + * same resource type, equal full paths, and identical workspaces. + * Resources are not equal to objects other than resources. + * + * @param other the other object + * @return an indication of whether the objects are equals + * @see #getType() + * @see #getFullPath() + * @see #getWorkspace() + */ + @Override + public boolean equals(Object other); + + /** + * Returns whether this resource exists in the workspace. + *

      + * IResource objects are lightweight handle objects + * used to access resources in the workspace. However, having a + * handle object does not necessarily mean the workspace really + * has such a resource. When the workspace does have a genuine + * resource of a matching type, the resource is said to + * exist, and this method returns true; + * in all other cases, this method returns false. + * In particular, it returns false if the workspace + * has no resource at that path, or if it has a resource at that + * path with a type different from the type of this resource handle. + *

      + *

      + * Note that no resources ever exist under a project + * that is closed; opening a project may bring some + * resources into existence. + *

      + *

      + * The name and path of a resource handle may be invalid. + * However, validation checks are done automatically as a + * resource is created; this means that any resource that exists + * can be safely assumed to have a valid name and path. + *

      + * + * @return true if the resource exists, otherwise + * false + */ + public boolean exists(); + + /** + * Returns the marker with the specified id on this resource, + * Returns null if there is no matching marker. + * + * @param id the id of the marker to find + * @return a marker or null + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is a project that is not open.
      • + *
      + */ + public IMarker findMarker(long id) throws CoreException; + + /** + * Returns all markers of the specified type on this resource, + * and, optionally, on its children. If includeSubtypes + * is false, only markers whose type exactly matches + * the given type are returned. Returns an empty array if there + * are no matching markers. + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param depth how far to recurse (see IResource.DEPTH_* ) + * @return an array of markers + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is a project that is not open.
      • + *
      + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + */ + public IMarker[] findMarkers(String type, boolean includeSubtypes, int depth) throws CoreException; + + /** + * Returns the maximum value of the {@link IMarker#SEVERITY} attribute across markers + * of the specified type on this resource, and, optionally, on its children. + * If includeSubtypesis false, only markers whose type + * exactly matches the given type are considered. + * Returns -1 if there are no matching markers. + * Returns {@link IMarker#SEVERITY_ERROR} if any of the markers has a severity + * greater than or equal to {@link IMarker#SEVERITY_ERROR}. + * + * @param type the type of marker to consider (normally {@link IMarker#PROBLEM} + * or one of its subtypes), or null to indicate all types + * + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param depth how far to recurse (see IResource.DEPTH_* ) + * @return {@link IMarker#SEVERITY_INFO}, {@link IMarker#SEVERITY_WARNING}, {@link IMarker#SEVERITY_ERROR}, or -1 + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is a project that is not open.
      • + *
      + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @since 3.3 + */ + public int findMaxProblemSeverity(String type, boolean includeSubtypes, int depth) throws CoreException; + + /** + * Returns the file extension portion of this resource's name, + * or null if it does not have one. + *

      + * The file extension portion is defined as the string + * following the last period (".") character in the name. + * If there is no period in the name, the path has no + * file extension portion. If the name ends in a period, + * the file extension portion is the empty string. + *

      + *

      + * This is a resource handle operation; the resource need + * not exist. + *

      + * + * @return a string file extension + * @see #getName() + */ + public String getFileExtension(); + + /** + * Returns the full, absolute path of this resource relative to the + * workspace. + *

      + * This is a resource handle operation; the resource need + * not exist. + * If this resource does exist, its path can be safely assumed to be valid. + *

      + *

      + * A resource's full path indicates the route from the root of the workspace + * to the resource. Within a workspace, there is exactly one such path + * for any given resource. The first segment of these paths name a project; + * remaining segments, folders and/or files within that project. + * The returned path never has a trailing separator. The path of the + * workspace root is Path.ROOT. + *

      + *

      + * Since absolute paths contain the name of the project, they are + * vulnerable when the project is renamed. For most situations, + * project-relative paths are recommended over absolute paths. + *

      + * + * @return the absolute path of this resource + * @see #getProjectRelativePath() + * @see Path#ROOT + */ + public IPath getFullPath(); + + /** + * Returns a cached value of the local time stamp on disk for this resource, or + * NULL_STAMP if the resource does not exist or is not local or is + * not accessible. The return value is represented as the number of milliseconds + * since the epoch (00:00:00 GMT, January 1, 1970). + * The returned value may not be the same as the actual time stamp + * on disk if the file has been modified externally since the last local refresh. + *

      + * Note that due to varying file system timing granularities, this value is not guaranteed + * to change every time the file is modified. For a more reliable indication of whether + * the file has changed, use getModificationStamp. + * + * @return a local file system time stamp, or NULL_STAMP. + * @since 3.0 + */ + public long getLocalTimeStamp(); + + /** + * Returns the absolute path in the local file system to this resource, + * or null if no path can be determined. + *

      + * If this resource is the workspace root, this method returns + * the absolute local file system path of the platform working area. + *

      + * If this resource is a project that exists in the workspace, this method + * returns the path to the project's local content area. This is true regardless + * of whether the project is open or closed. This value will be null in the case + * where the location is relative to an undefined workspace path variable. + *

      + * If this resource is a linked resource under a project that is open, this + * method returns the resolved path to the linked resource's local contents. + * This value will be null in the case where the location is relative to an + * undefined workspace path variable. + *

      + * If this resource is a file or folder under a project that exists, or a + * linked resource under a closed project, this method returns a (non- + * null) path computed from the location of the project's local + * content area and the project- relative path of the file or folder. This is + * true regardless of whether the file or folders exists, or whether the project + * is open or closed. In the case of linked resources, the location of a linked resource + * within a closed project is too computed from the location of the + * project's local content area and the project-relative path of the resource. If the + * linked resource resides in an open project then its location is computed + * according to the link. + *

      + * If this resource is a project that does not exist in the workspace, + * or a file or folder below such a project, this method returns + * null. This method also returns null if called + * on a resource that is not stored in the local file system. For such resources + * {@link #getLocationURI()} should be used instead. + *

      + * + * @return the absolute path of this resource in the local file system, + * or null if no path can be determined + * @see #getRawLocation() + * @see #getLocationURI() + * @see IProjectDescription#setLocation(IPath) + * @see Platform#getLocation() + */ + public IPath getLocation(); + + /** + * Returns the absolute URI of this resource, + * or null if no URI can be determined. + *

      + * If this resource is the workspace root, this method returns + * the absolute location of the platform working area. + *

      + * If this resource is a project that exists in the workspace, this method + * returns the URI to the project's local content area. This is true regardless + * of whether the project is open or closed. This value will be null in the case + * where the location is relative to an undefined workspace path variable. + *

      + * If this resource is a linked resource under a project that is open, this + * method returns the resolved URI to the linked resource's local contents. + * This value will be null in the case where the location is relative to an + * undefined workspace path variable. + *

      + * If this resource is a file or folder under a project that exists, or a + * linked resource under a closed project, this method returns a (non- + * null) URI computed from the location of the project's local + * content area and the project- relative path of the file or folder. This is + * true regardless of whether the file or folders exists, or whether the project + * is open or closed. In the case of linked resources, the location of a linked resource + * within a closed project is computed from the location of the + * project's local content area and the project-relative path of the resource. If the + * linked resource resides in an open project then its location is computed + * according to the link. + *

      + * If this resource is a project that does not exist in the workspace, + * or a file or folder below such a project, this method returns + * null. + *

      + * + * @return the absolute URI of this resource, + * or null if no URI can be determined + * @see #getRawLocation() + * @see IProjectDescription#setLocation(IPath) + * @see Platform#getLocation() + * @see java.net.URI + * @since 3.2 + */ + public URI getLocationURI(); + + /** + * Returns a marker handle with the given id on this resource. + * This resource is not checked to see if it has such a marker. + * The returned marker need not exist. + * This resource need not exist. + * + * @param id the id of the marker + * @return the specified marker handle + * @see IMarker#getId() + */ + public IMarker getMarker(long id); + + /** + * Returns a non-negative modification stamp, or NULL_STAMP if + * the resource does not exist or is not local or is not accessible. + *

      + * A resource's modification stamp gets updated each time a resource is modified. + * If a resource's modification stamp is the same, the resource has not changed. + * Conversely, if a resource's modification stamp is different, some aspect of it + * (other than properties) has been modified at least once (possibly several times). + * Resource modification stamps are preserved across project close/re-open, + * and across workspace shutdown/restart. + * The magnitude or sign of the numerical difference between two modification stamps + * is not significant. + *

      + *

      + * The following things affect a resource's modification stamp: + *

        + *
      • creating a non-project resource (changes from NULL_STAMP)
      • + *
      • changing the contents of a file
      • + *
      • touching a resource
      • + *
      • setting the attributes of a project presented in a project description
      • + *
      • deleting a resource (changes to NULL_STAMP)
      • + *
      • moving a resource (source changes to NULL_STAMP, + destination changes from NULL_STAMP)
      • + *
      • copying a resource (destination changes from NULL_STAMP)
      • + *
      • making a resource local
      • + *
      • closing a project (changes to NULL_STAMP)
      • + *
      • opening a project (changes from NULL_STAMP)
      • + *
      • adding or removing a project nature (changes from NULL_STAMP)
      • + *
      + * The following things do not affect a resource's modification stamp: + *
        + *
      • "reading" a resource
      • + *
      • adding or removing a member of a project or folder
      • + *
      • setting a session property
      • + *
      • setting a persistent property
      • + *
      • saving the workspace
      • + *
      • shutting down and re-opening a workspace
      • + *
      + *

      + * + * @return the modification stamp, or NULL_STAMP if this resource either does + * not exist or exists as a closed project + * @see IResource#NULL_STAMP + * @see #revertModificationStamp(long) + */ + public long getModificationStamp(); + + /** + * Returns the name of this resource. + * The name of a resource is synonymous with the last segment + * of its full (or project-relative) path for all resources other than the + * workspace root. The workspace root's name is the empty string. + *

      + * This is a resource handle operation; the resource need + * not exist. + *

      + *

      + * If this resource exists, its name can be safely assumed to be valid. + *

      + * + * @return the name of the resource + * @see #getFullPath() + * @see #getProjectRelativePath() + */ + public String getName(); + + /** + * Returns the path variable manager for this resource. + * + * @return the path variable manager + * @see IPathVariableManager + * @since 3.6 + */ + public IPathVariableManager getPathVariableManager(); + + /** + * Returns the resource which is the parent of this resource, + * or null if it has no parent (that is, this + * resource is the workspace root). + *

      + * The full path of the parent resource is the same as this + * resource's full path with the last segment removed. + *

      + *

      + * This is a resource handle operation; neither the resource + * nor the resulting resource need exist. + *

      + * + * @return the parent resource of this resource, + * or null if it has no parent + */ + public IContainer getParent(); + + /** + * Returns a copy of the map of this resource's persistent properties. + * Returns an empty map if this resource has no persistent properties. + * + * @return the map containing the persistent properties where the key is + * the {@link QualifiedName} of the property and the value is the {@link String} + * value of the property. + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is not local.
      • + *
      • This resource is a project that is not open.
      • + *
      + * @see #setPersistentProperty(QualifiedName, String) + * @since 3.4 + */ + public Map getPersistentProperties() throws CoreException; + + /** + * Returns the value of the persistent property of this resource identified + * by the given key, or null if this resource has no such property. + * + * @param key the qualified name of the property + * @return the string value of the property, + * or null if this resource has no such property + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is not local.
      • + *
      • This resource is a project that is not open.
      • + *
      + * @see #setPersistentProperty(QualifiedName, String) + */ + public String getPersistentProperty(QualifiedName key) throws CoreException; + + /** + * Returns the project which contains this resource. + * Returns itself for projects and null + * for the workspace root. + *

      + * A resource's project is the one named by the first segment + * of its full path. + *

      + *

      + * This is a resource handle operation; neither the resource + * nor the resulting project need exist. + *

      + * + * @return the project handle + */ + public IProject getProject(); + + /** + * Returns a relative path of this resource with respect to its project. + * Returns the empty path for projects and the workspace root. + *

      + * This is a resource handle operation; the resource need not exist. + * If this resource does exist, its path can be safely assumed to be valid. + *

      + *

      + * A resource's project-relative path indicates the route from the project + * to the resource. Within a project, there is exactly one such path + * for any given resource. The returned path never has a trailing slash. + *

      + *

      + * Project-relative paths are recommended over absolute paths, since + * the former are not affected if the project is renamed. + *

      + * + * @return the relative path of this resource with respect to its project + * @see #getFullPath() + * @see #getProject() + * @see Path#EMPTY + */ + public IPath getProjectRelativePath(); + + /** + * Returns the file system location of this resource, or null if no + * path can be determined. The returned path will either be an absolute file + * system path, or a relative path whose first segment is the name of a + * workspace path variable. + *

      + * If this resource is an existing project, the returned path will be equal to + * the location path in the project description. If this resource is a linked + * resource in an open project, the returned path will be equal to the location + * path supplied when the linked resource was created. In all other cases, this + * method returns the same value as {@link #getLocation()}. + *

      + * + * @return the raw path of this resource in the local file system, or + * null if no path can be determined + * @see #getLocation() + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @see IPathVariableManager + * @see IProjectDescription#getLocation() + * @since 2.1 + */ + public IPath getRawLocation(); + + /** + * Returns the raw location of this resource, or null if no + * path can be determined. The returned path will either be an absolute URI, + * or a relative URI whose first path segment is the name of a workspace path variable. + * Since the returned location may contain unresolved variables, the resulting URI + * is typically only suitable for display. To access or manipulate the actual resource + * backing location, clients should obtain the resolved location using {@link #getLocationURI()}. + *

      + * If this resource is an existing project, the returned location will be equal to + * the location URI in the project description. If this resource is a linked + * resource in an open project, the returned location will be equal to the location URI + * supplied when the linked resource was created. In all other cases, this + * method returns the same value as {@link #getLocationURI()}. + *

      + * + * @return the raw location of this resource, or null if no + * location can be determined + * @see #getLocationURI() + * @see IFile#createLink(URI, int, IProgressMonitor) + * @see IFolder#createLink(URI, int, IProgressMonitor) + * @see IPathVariableManager + * @see IProjectDescription#getLocationURI() + * @since 3.2 + */ + public URI getRawLocationURI(); + + /** + * Gets this resource's extended attributes from the file system, + * or null if the attributes could not be obtained. + *

      + * Reasons for a null return value include: + *

        + *
      • This resource does not exist.
      • + *
      • This resource is not local.
      • + *
      • This resource is a project that is not open.
      • + *
      + *

      + * Attributes that are not supported by the underlying file system + * will have a value of false. + *

      + * Sample usage:
      + *
      + * + * IResource resource;
      + * ...
      + * ResourceAttributes attributes = resource.getResourceAttributes();
      + * if (attributes != null) { + * attributes.setExecutable(true);
      + * resource.setResourceAttributes(attributes);
      + * } + *
      + *

      + * + * @return the extended attributes from the file system, or + * null if they could not be obtained + * @see #setResourceAttributes(ResourceAttributes) + * @see ResourceAttributes + * @since 3.1 + */ + public ResourceAttributes getResourceAttributes(); + + /** + * Returns a copy of the map of this resource's session properties. + * Returns an empty map if this resource has no session properties. + * + * @return the map containing the session properties where the key is + * the {@link QualifiedName} of the property and the value is the property + * value (an {@link Object}. + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is not local.
      • + *
      • This resource is a project that is not open.
      • + *
      + * @see #setSessionProperty(QualifiedName, Object) + * @since 3.4 + */ + public Map getSessionProperties() throws CoreException; + + /** + * Returns the value of the session property of this resource identified + * by the given key, or null if this resource has no such property. + * + * @param key the qualified name of the property + * @return the value of the session property, + * or null if this resource has no such property + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is not local.
      • + *
      • This resource is a project that is not open.
      • + *
      + * @see #setSessionProperty(QualifiedName, Object) + */ + public Object getSessionProperty(QualifiedName key) throws CoreException; + + /** + * Returns the type of this resource. + * The returned value will be one of FILE, + * FOLDER, PROJECT, ROOT. + *

      + *

        + *
      • All resources of type FILE implement IFile.
      • + *
      • All resources of type FOLDER implement IFolder.
      • + *
      • All resources of type PROJECT implement IProject.
      • + *
      • All resources of type ROOT implement IWorkspaceRoot.
      • + *
      + *

      + *

      + * This is a resource handle operation; the resource need + * not exist in the workspace. + *

      + * + * @return the type of this resource + * @see #FILE + * @see #FOLDER + * @see #PROJECT + * @see #ROOT + */ + public int getType(); + + /** + * Returns the workspace which manages this resource. + *

      + * This is a resource handle operation; the resource need + * not exist in the workspace. + *

      + * + * @return the workspace + */ + public IWorkspace getWorkspace(); + + /** + * Returns whether this resource is accessible. For files and folders, + * this is equivalent to existing; for projects, + * this is equivalent to existing and being open. The workspace root + * is always accessible. + * + * @return true if this resource is accessible, and + * false otherwise + * @see #exists() + * @see IProject#isOpen() + */ + public boolean isAccessible(); + + /** + * Returns whether this resource subtree is marked as derived. Returns + * false if this resource does not exist. + * + *

      + * This is a convenience method, + * fully equivalent to isDerived(IResource.NONE). + *

      + * + * @return true if this resource is marked as derived, and + * false otherwise + * @see #setDerived(boolean) + * @since 2.0 + */ + public boolean isDerived(); + + /** + * Returns whether this resource subtree is marked as derived. Returns + * false if this resource does not exist. + * + *

      + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true, if this resource, or any parent resource, is marked + * as derived. If the {@link #CHECK_ANCESTORS} option flag is not specified, + * this method returns false for children of derived resources. + *

      + * + * @param options bit-wise or of option flag constants + * (only {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource subtree is derived, and + * false otherwise + * @see IResource#setDerived(boolean) + * @since 3.4 + */ + public boolean isDerived(int options); + + /** + * Returns whether this resource is hidden in the resource tree. Returns + * false if this resource does not exist. + *

      + * This operation is not related to the file system hidden attribute accessible using + * {@link ResourceAttributes#isHidden()}. + *

      + * + * @return true if this resource is hidden , and + * false otherwise + * @see #setHidden(boolean) + * @since 3.4 + */ + public boolean isHidden(); + + /** + * Returns whether this resource is hidden in the resource tree. Returns + * false if this resource does not exist. + *

      + * This operation is not related to the file system hidden attribute + * accessible using {@link ResourceAttributes#isHidden()}. + *

      + *

      + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true if this resource, or any parent resource, is a hidden + * resource. If the {@link #CHECK_ANCESTORS} option flag is not specified, + * this method returns false for children of hidden resources. + *

      + * + * @param options + * bit-wise or of option flag constants (only + * {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource is hidden , and + * false otherwise + * @see #setHidden(boolean) + * @since 3.5 + */ + public boolean isHidden(int options); + + /** + * Returns whether this resource has been linked to + * a location other than the default location calculated by the platform. + *

      + * This is a convenience method, fully equivalent to + * isLinked(IResource.NONE). + *

      + * + * @return true if this resource is linked, and + * false otherwise + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @since 2.1 + */ + public boolean isLinked(); + + /** + * Returns whether this resource is a virtual resource. Returns true + * for folders that have been marked virtual using the {@link #VIRTUAL} update + * flag. Returns false in all other cases, including + * the case where this resource does not exist. The workspace root, projects + * and files currently cannot be made virtual. + * + * @return true if this resource is virtual, and + * false otherwise + * @see IFile#create(java.io.InputStream, int, IProgressMonitor) + * @see #VIRTUAL + * @since 3.6 + */ + public boolean isVirtual(); + + /** + * Returns true if this resource has been linked to + * a location other than the default location calculated by the platform. This + * location can be outside the project's content area or another location + * within the project. Returns false in all other cases, including + * the case where this resource does not exist. The workspace root and + * projects are never linked. + *

      + * This method returns true for a resource that has been linked using + * the createLink method. + *

      + *

      + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true if this resource, or any parent resource, is a linked + * resource. If the {@link #CHECK_ANCESTORS} option flag is not specified, + * this method returns false for children of linked resources. + *

      + * + * @param options bit-wise or of option flag constants + * (only {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource is linked, and + * false otherwise + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @since 3.2 + */ + public boolean isLinked(int options); + + /** + * Returns whether this resource and its members (to the + * specified depth) are expected to have their contents (and properties) + * available locally. Returns false in all other cases, + * including the case where this resource does not exist. The workspace + * root and projects are always local. + *

      + * When a resource is not local, its content and properties are + * unavailable for both reading and writing. + *

      + * + * @param depth valid values are DEPTH_ZERO, + * DEPTH_ONE, or DEPTH_INFINITE + * @return true if this resource is local, and + * false otherwise + * + * @see #setLocal(boolean, int, IProgressMonitor) + * @deprecated This API is no longer in use. Note that this API is unrelated + * to whether the resource is in the local file system versus some other file system. + */ + @Deprecated + public boolean isLocal(int depth); + + /** + * Returns whether this resource is a phantom resource. + *

      + * The workspace uses phantom resources to remember outgoing deletions and + * incoming additions relative to an external synchronization partner. Phantoms + * appear and disappear automatically as a byproduct of synchronization. + * Since the workspace root cannot be synchronized in this way, it is never a phantom. + * Projects are also never phantoms. + *

      + *

      + * The key point is that phantom resources do not exist (in the technical + * sense of exists, which returns false + * for phantoms) are therefore invisible except through a handful of + * phantom-enabled API methods (notably IContainer.members(boolean)). + *

      + * + * @return true if this resource is a phantom resource, and + * false otherwise + * @see #exists() + * @see IContainer#members(boolean) + * @see IContainer#findMember(String, boolean) + * @see IContainer#findMember(IPath, boolean) + * @see ISynchronizer + */ + public boolean isPhantom(); + + /** + * Returns whether this resource is marked as read-only in the file system. + * + * @return true if this resource is read-only, + * false otherwise + * @deprecated use {@link #getResourceAttributes()} + */ + @Deprecated + public boolean isReadOnly(); + + /** + * Returns whether this resource and its descendents to the given depth + * are considered to be in sync with the local file system. + *

      + * A resource is considered to be in sync if all of the following + * conditions are true: + *

        + *
      • The resource exists in both the workspace and the file system.
      • + *
      • The timestamp in the file system has not changed since the + * last synchronization.
      • + *
      • The resource in the workspace is of the same type as the corresponding + * file in the file system (they are either both files or both folders).
      • + *
      + * A resource is also considered to be in sync if it is missing from both + * the workspace and the file system. In all other cases the resource is + * considered to be out of sync. + *

      + *

      + * This operation interrogates files and folders in the local file system; + * depending on the speed of the local file system and the requested depth, + * this operation may be time-consuming. + *

      + * + * @param depth the depth (one of IResource.DEPTH_ZERO, + * DEPTH_ONE, or DEPTH_INFINITE) + * @return true if this resource and its descendents to the + * specified depth are synchronized, and false in all other + * cases + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see #refreshLocal(int, IProgressMonitor) + * @since 2.0 + */ + public boolean isSynchronized(int depth); + + /** + * Returns whether this resource is a team private member of its parent container. + * Returns false if this resource does not exist. + * + * @return true if this resource is a team private member, and + * false otherwise + * @see #setTeamPrivateMember(boolean) + * @since 2.0 + */ + public boolean isTeamPrivateMember(); + + /** + * Returns whether this resource is a team private member of its parent + * container. Returns false if this resource does not exist. + *

      + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true if this resource, or any parent resource, is a team + * private member. If the {@link #CHECK_ANCESTORS} option flag is not + * specified, this method returns false for children of team private + * members. + *

      + * + * @param options + * bit-wise or of option flag constants (only + * {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource is a team private member, and + * false otherwise + * @see #setTeamPrivateMember(boolean) + * @since 3.5 + */ + public boolean isTeamPrivateMember(int options); + + /** + * Moves this resource so that it is located at the given path. + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   move(destination, force ? FORCE : IResource.NONE, monitor);
      +	 * 
      + *

      + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource has been removed from its parent + * and that a corresponding resource has been added to its new parent. + * Additional information provided with resource delta shows that these + * additions and removals are related. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource or one of its descendents is not local.
      • + *
      • The source or destination is the workspace root.
      • + *
      • The source is a project but the destination is not.
      • + *
      • The destination is a project but the source is not.
      • + *
      • The resource corresponding to the parent destination path does not exist.
      • + *
      • The resource corresponding to the parent destination path is a closed + * project.
      • + *
      • A resource at destination path does exist.
      • + *
      • A resource of a different type exists at the destination path.
      • + *
      • This resource or one of its descendents is out of sync with the local file + * system and force is false.
      • + *
      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      • The source resource is a file and the destination path specifies a project.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + */ + public void move(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Moves this resource so that it is located at the given path. + * The path of the resource must not be a prefix of the destination path. The + * workspace root may not be the source or destination location of a move + * operation, and a project can only be moved to another project. After + * successful completion, the resource and any direct or indirect members will + * no longer exist; but corresponding new resources will now exist at the given + * path. + *

      + * The supplied path may be absolute or relative. Absolute paths fully specify + * the new location for the resource, including its project. Relative paths are + * considered to be relative to the container of the resource being moved. A + * trailing slash is ignored. + *

      + *

      + * Calling this method with a one segment absolute destination path is + * equivalent to calling: + *

      +	 IProjectDescription description = getDescription();
      +	 description.setName(path.lastSegment());
      +	 move(description, updateFlags, monitor);
      +	 * 
      + *

      + *

      When a resource moves, its session and persistent properties move with + * it. Likewise for all other attributes of the resource including markers. + *

      + *

      + * The FORCE update flag controls how this method deals with cases + * where the workspace is not completely in sync with the local file system. If + * FORCE is not specified, the method will only attempt to move + * resources that are in sync with the corresponding files and directories in + * the local file system; it will fail if it encounters a resource that is out + * of sync with the file system. However, if FORCE is specified, + * the method moves all corresponding files and directories from the local file + * system, including ones that have been recently updated or created. Note that + * in both settings of the FORCE flag, the operation fails if the + * newly created resources in the workspace would be out of sync with the local + * file system; this ensures files in the file system cannot be accidentally + * overwritten. + *

      + *

      + * The KEEP_HISTORY update flag controls whether or not + * file that are about to be deleted from the local file system have their + * current contents saved in the workspace's local history. The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying KEEP_HISTORY + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. Hence KEEP_HISTORY is only really applicable + * when moving files and folders, but not whole projects. + *

      + *

      + * If this resource is not a project, an attempt will be made to copy the local history + * for this resource and its children, to the destination. Since local history existence + * is a safety-net mechanism, failure of this action will not result in automatic failure + * of the move operation. + *

      + *

      + * The SHALLOW update flag controls how this method deals with linked + * resources. If SHALLOW is not specified, then the underlying + * contents of the linked resource will always be moved in the file system. In + * this case, the destination of the move will never be a linked resource or + * contain any linked resources. If SHALLOW is specified when a + * linked resource is moved into another project, a new linked resource is + * created in the destination project that points to the same file system + * location. When a project containing linked resources is moved, the new + * project will contain the same linked resources pointing to the same file + * system locations. For either of these cases, no files on disk under the + * linked resource are actually moved. With the SHALLOW flag, + * moving of linked resources into anything other than a project is not + * permitted. The SHALLOW update flag is ignored when moving non- + * linked resources. + *

      + *

      + * Update flags other than FORCE, KEEP_HISTORYand + * SHALLOW are ignored. + *

      + *

      + * This method changes resources; these changes will be reported in a subsequent + * resource change event that will include an indication that the resource has + * been removed from its parent and that a corresponding resource has been added + * to its new parent. Additional information provided with resource delta shows + * that these additions and removals are related. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param destination the destination path + * @param updateFlags bit-wise or of update flag constants + * (FORCE, KEEP_HISTORY and SHALLOW) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource or one of its descendents is not local.
      • + *
      • The source or destination is the workspace root.
      • + *
      • The source is a project but the destination is not.
      • + *
      • The destination is a project but the source is not.
      • + *
      • The resource corresponding to the parent destination path does not exist.
      • + *
      • The resource corresponding to the parent destination path is a closed + * project.
      • + *
      • The source is a linked resource, but the destination is not a project + * and SHALLOW is specified.
      • + *
      • A resource at destination path does exist.
      • + *
      • A resource of a different type exists at the destination path.
      • + *
      • This resource or one of its descendents is out of sync with the local file system + * and force is false.
      • + *
      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
      • + *
      • The source resource is a file and the destination path specifies a project.
      • + *
      • The location of the source resource on disk is the same or a prefix of + * the location of the destination resource on disk.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + * @see #FORCE + * @see #KEEP_HISTORY + * @see #SHALLOW + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @since 2.0 + */ + public void move(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Renames or relocates this project so that it is the project specified by the given project + * description. + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   move(description, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
      +	 * 
      + *

      + *

      + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource has been removed from its parent + * and that a corresponding resource has been added to its new parent. + * Additional information provided with resource delta shows that these + * additions and removals are related. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param description the destination project description + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag indicating whether or not to keep + * local history for files + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource or one of its descendents is not local.
      • + *
      • This resource is not a project.
      • + *
      • The project at the destination already exists.
      • + *
      • This resource or one of its descendents is out of sync with the local file + * system and force is false.
      • + *
      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + */ + public void move(IProjectDescription description, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Renames or relocates this project so that it is the project specified by the + * given project description. The description specifies the name and location + * of the new project. After successful completion, the old project + * and any direct or indirect members will no longer exist; but corresponding + * new resources will now exist in the new project. + *

      + * When a resource moves, its session and persistent properties move with it. + * Likewise for all the other attributes of the resource including markers. + *

      + *

      + * When this project's location is the default location, then the directories + * and files on disk are moved to be in the location specified by the given + * description. If the given description specifies the default location for the + * project, the directories and files are moved to the default location. If the name + * in the given description is the same as this project's name and the location + * is different, then the project contents will be moved to the new location. + * In all other cases the directories and files on disk are left untouched. + * Parts of the supplied description other than the name and location are ignored. + *

      + *

      + * The FORCE update flag controls how this method deals with cases + * where the workspace is not completely in sync with the local file system. If + * FORCE is not specified, the method will only attempt to move + * resources that are in sync with the corresponding files and directories in + * the local file system; it will fail if it encounters a resource that is out + * of sync with the file system. However, if FORCE is specified, + * the method moves all corresponding files and directories from the local file + * system, including ones that have been recently updated or created. Note that + * in both settings of the FORCE flag, the operation fails if the + * newly created resources in the workspace would be out of sync with the local + * file system; this ensures files in the file system cannot be accidentally + * overwritten. + *

      + *

      + * The KEEP_HISTORY update flag controls whether or not file that + * are about to be deleted from the local file system have their current + * contents saved in the workspace's local history. The local history mechanism + * serves as a safety net to help the user recover from mistakes that might + * otherwise result in data loss. Specifying KEEP_HISTORY is + * recommended except in circumstances where past states of the files are of no + * conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. Hence KEEP_HISTORY is only really applicable + * when moving files and folders, but not whole projects. + *

      + *

      + * Local history information for this project and its children will not be moved to the + * destination. + *

      + *

      + * The SHALLOW update flag controls how this method deals with linked + * resources. If SHALLOW is not specified, then the underlying + * contents of any linked resource will always be moved in the file system. In + * this case, the destination of the move will not contain any linked resources. + * If SHALLOW is specified when a project containing linked + * resources is moved, new linked resources are created in the destination + * project pointing to the same file system locations. In this case, no files + * on disk under any linked resource are actually moved. The + * SHALLOW update flag is ignored when moving non- linked + * resources. + *

      + *

      + * The {@link #REPLACE} update flag controls how this method deals + * with a change of location. If the location changes and the {@link #REPLACE} + * flag is not specified, then the projects contents on disk are moved to the new + * location. If the location changes and the {@link #REPLACE} + * flag is specified, then the project is reoriented to correspond to the new + * location, but no contents are moved on disk. The contents already on + * disk at the new location become the project contents. If the new project + * location does not exist, it will be created. + *

      + *

      + * Update flags other than those listed above are ignored. + *

      + *

      + * This method changes resources; these changes will be reported in a subsequent + * resource change event that will include an indication that the resource has + * been removed from its parent and that a corresponding resource has been added + * to its new parent. Additional information provided with resource delta shows + * that these additions and removals are related. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param description the destination project description + * @param updateFlags bit-wise or of update flag constants + * ({@link #FORCE}, {@link #KEEP_HISTORY}, {@link #SHALLOW} + * and {@link #REPLACE}). + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource or one of its descendents is not local.
      • + *
      • This resource is not a project.
      • + *
      • The project at the destination already exists.
      • + *
      • This resource or one of its descendents is out of sync with the + * local file system and FORCE is not specified.
      • + *
      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      • The destination file system location is occupied. When moving a project + * in the file system, the destination directory must either not exist or be empty.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + * @see #FORCE + * @see #KEEP_HISTORY + * @see #SHALLOW + * @see #REPLACE + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @since 2.0 + */ + public void move(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Refreshes the resource hierarchy from this resource and its + * children (to the specified depth) relative to the local file system. + * Creations, deletions, and changes detected in the local file system + * will be reflected in the workspace's resource tree. + * This resource need not exist or be local. + *

      + * This method may discover changes to resources; any such + * changes will be reported in a subsequent resource change event. + *

      + *

      + * If a new file or directory is discovered in the local file + * system at or below the location of this resource, + * any parent folders required to contain the new + * resource in the workspace will also be created automatically as required. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param depth valid values are DEPTH_ZERO, + * DEPTH_ONE, or DEPTH_INFINITE + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceRuleFactory#refreshRule(IResource) + */ + public void refreshLocal(int depth, IProgressMonitor monitor) throws CoreException; + + /** + * Reverts this resource's modification stamp. This is intended to be used by + * a client that is rolling back or undoing a previous change to this resource. + *

      + * It is the caller's responsibility to ensure that the value of the reverted + * modification stamp matches this resource's modification stamp prior to the + * change that has been rolled back. More generally, the caller must ensure + * that the specification of modification stamps outlined in + * getModificationStamp is honored; the modification stamp + * of two distinct resource states should be different if and only if one or more + * of the attributes listed in the specification as affecting the modification + * stamp have changed. + *

      + * Reverting the modification stamp will not be reported in a + * subsequent resource change event. + *

      + * Note that a resource's modification stamp is unrelated to the local + * time stamp for this resource on disk, if any. A resource's local time + * stamp is modified using the setLocalTimeStamp method. + * + * @param value A non-negative modification stamp value + * @exception CoreException if this method fails. Reasons include: + *

        + *
      • This resource does not exist.
      • + *
      • This resource is not local.
      • + *
      • This resource is not accessible.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see #getModificationStamp() + * @since 3.1 + */ + public void revertModificationStamp(long value) throws CoreException; + + /** + * Sets whether this resource subtree is marked as derived. + *

      + * A derived resource is a regular file or folder that is + * created in the course of translating, compiling, copying, or otherwise + * processing other files. Derived resources are not original data, and can be + * recreated from other resources. It is commonplace to exclude derived + * resources from version and configuration management because they would + * otherwise clutter the team repository with version of these ever-changing + * files as each user regenerates them. + *

      + *

      + * If a resource or any of its ancestors is marked as derived, a team + * provider should assume that the resource is not under version and + * configuration management by default. That is, the resource + * should only be stored in a team repository if the user explicitly indicates + * that this resource is worth saving. + *

      + *

      + * Newly-created resources are not marked as derived; rather, the mark must be + * set explicitly using setDerived(true). Derived marks are maintained + * in the in-memory resource tree, and are discarded when the resources are deleted. + * Derived marks are saved to disk when a project is closed, or when the workspace + * is saved. + *

      + *

      + * Projects and the workspace root are never considered derived; attempts to + * mark them as derived are ignored. + *

      + *

      + * This operation does not result in a resource change event, and does not + * trigger autobuilds. + *

      + * + * @param isDerived true if this resource is to be marked + * as derived, and false otherwise + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see #isDerived() + * @since 2.0 + * @deprecated Replaced by {@link #setDerived(boolean, IProgressMonitor)} which + * is a workspace operation and reports changes in resource deltas. + */ + @Deprecated + public void setDerived(boolean isDerived) throws CoreException; + + /** + * Sets whether this resource subtree is marked as derived. + *

      + * A derived resource is a regular file or folder that is + * created in the course of translating, compiling, copying, or otherwise + * processing other files. Derived resources are not original data, and can be + * recreated from other resources. It is commonplace to exclude derived + * resources from version and configuration management because they would + * otherwise clutter the team repository with version of these ever-changing + * files as each user regenerates them. + *

      + *

      + * If a resource or any of its ancestors is marked as derived, a team + * provider should assume that the resource is not under version and + * configuration management by default. That is, the resource + * should only be stored in a team repository if the user explicitly indicates + * that this resource is worth saving. + *

      + *

      + * Newly-created resources are not marked as derived; rather, the mark must be + * set explicitly using setDerived(true, IProgressMonitor). Derived marks are maintained + * in the in-memory resource tree, and are discarded when the resources are deleted. + * Derived marks are saved to disk when a project is closed, or when the workspace + * is saved. + *

      + *

      + * Projects and the workspace root are never considered derived; attempts to + * mark them as derived are ignored. + *

      + *

      + * These changes will be reported in a subsequent resource change event, + * including an indication that this file's derived flag has changed. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param isDerived true if this resource is to be marked + * as derived, and false otherwise + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see #isDerived() + * @see IResourceRuleFactory#derivedRule(IResource) + * @since 3.6 + */ + public void setDerived(boolean isDerived, IProgressMonitor monitor) throws CoreException; + + /** + * Sets whether this resource and its members are hidden in the resource tree. + *

      + * Hidden resources are invisible to most clients. Newly-created resources + * are not hidden resources by default. + *

      + *

      + * The workspace root is never considered hidden resource; + * attempts to mark it as hidden are ignored. + *

      + *

      + * This operation does not result in a resource change event, and does not + * trigger autobuilds. + *

      + *

      + * This operation is not related to {@link ResourceAttributes#setHidden(boolean)}. + * Whether a resource is hidden in the resource tree is unrelated to whether the + * underlying file is hidden in the file system. + *

      + * + * @param isHidden true if this resource is to be marked + * as hidden, and false otherwise + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see #isHidden() + * @since 3.4 + */ + public void setHidden(boolean isHidden) throws CoreException; + + /** + * Set whether or not this resource and its members (to the + * specified depth) are expected to have their contents (and properties) + * available locally. The workspace root and projects are always local and + * attempting to set either to non-local (i.e., passing false) + * has no effect on the resource. + *

      + * When a resource is not local, its content and properties are + * unavailable for both reading and writing. + *

      + *

      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param flag whether this resource should be considered local + * @param depth valid values are DEPTH_ZERO, + * DEPTH_ONE, or DEPTH_INFINITE + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See {@link IResourceChangeEvent} for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #isLocal(int) + * @deprecated This API is no longer in use. Note that this API is unrelated + * to whether the resource is in the local file system versus some other file system. + */ + @Deprecated + public void setLocal(boolean flag, int depth, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the local time stamp on disk for this resource. The time must be represented + * as the number of milliseconds since the epoch (00:00:00 GMT, January 1, 1970). + * Returns the actual time stamp that was recorded. + * Due to varying file system timing granularities, the provided value may be rounded + * or otherwise truncated, so the actual recorded time stamp that is returned may + * not be the same as the supplied value. + * + * @param value a time stamp in milliseconds. + * @return a local file system time stamp. + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is not local.
      • + *
      • This resource is not accessible.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @since 3.0 + */ + public long setLocalTimeStamp(long value) throws CoreException; + + /** + * Sets the value of the persistent property of this resource identified + * by the given key. If the supplied value is null, + * the persistent property is removed from this resource. The change + * is made immediately on disk. + *

      + * Persistent properties are intended to be used by plug-ins to store + * resource-specific information that should be persisted across platform sessions. + * The value of a persistent property is a string that must be short - + * 2KB or less in length. Unlike session properties, persistent properties are + * stored on disk and maintained across workspace shutdown and restart. + *

      + *

      + * 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 string value of the property, + * or null if the property is to be removed + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is not local.
      • + *
      • This resource is a project that is not open.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see #getPersistentProperty(QualifiedName) + * @see #isLocal(int) + */ + public void setPersistentProperty(QualifiedName key, String value) throws CoreException; + + /** + * Sets or unsets this resource as read-only in the file system. + * + * @param readOnly true to set it to read-only, + * false to unset + * @deprecated use IResource#setResourceAttributes(ResourceAttributes) + */ + @Deprecated + public void setReadOnly(boolean readOnly); + + /** + * Sets this resource with the given extended attributes. This sets the + * attributes in the file system. Only attributes that are supported by + * the underlying file system will be set. + *

      + * Sample usage:
      + *
      + * + * IResource resource;
      + * ...
      + * if (attributes != null) { + * attributes.setExecutable(true);
      + * resource.setResourceAttributes(attributes);
      + * } + *
      + *

      + *

      + * Note that a resource cannot be converted into a symbolic link by + * setting resource attributes with {@link ResourceAttributes#isSymbolicLink()} + * set to true. + *

      + * + * @param attributes the attributes to set + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is not local.
      • + *
      • This resource is a project that is not open.
      • + *
      + * @see #getResourceAttributes() + * @since 3.1 + */ + void setResourceAttributes(ResourceAttributes attributes) throws CoreException; + + /** + * Sets the value of the session property of this resource identified + * by the given key. If the supplied value is null, + * the session property is removed from this resource. + *

      + * Sessions properties are intended to be used as a caching mechanism + * by ISV plug-ins. They allow key-object associations to be stored with + * existing resources in the workspace. These key-value associations are + * maintained in memory (at all times), and the information is lost when a + * resource is deleted from the workspace, when the parent project + * is closed, or when the workspace is closed. + *

      + *

      + * 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 session property, + * or null if the property is to be removed + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is not local.
      • + *
      • This resource is a project that is not open.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see #getSessionProperty(QualifiedName) + */ + public void setSessionProperty(QualifiedName key, Object value) throws CoreException; + + /** + * Sets whether this resource subtree is a team private member of its parent container. + *

      + * A team private member resource is a special file or folder created by a team + * provider to hold team-provider-specific information. Resources marked as team private + * members are invisible to most clients. + *

      + *

      + * Newly-created resources are not team private members by default; rather, the + * team provider must mark a resource explicitly using + * setTeamPrivateMember(true). Team private member marks are + * maintained in the in-memory resource tree, and are discarded when the + * resources are deleted. Team private member marks are saved to disk when a + * project is closed, or when the workspace is saved. + *

      + *

      + * Projects and the workspace root are never considered team private members; + * attempts to mark them as team private are ignored. + *

      + *

      + * This operation does not result in a resource change event, and does not + * trigger autobuilds. + *

      + * + * @param isTeamPrivate true if this resource is to be marked + * as team private, and false otherwise + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @see #isTeamPrivateMember() + * @since 2.0 + */ + public void setTeamPrivateMember(boolean isTeamPrivate) throws CoreException; + + /** + * Marks this resource as having changed even though its content + * may not have changed. This method can be used to trigger + * the rebuilding of resources/structures derived from this resource. + * Touching the workspace root has no effect. + *

      + * This method changes resources; these changes will be reported + * in a subsequent resource change event. If the resource is a project, + * the change event will indicate a description change. + *

      + *

      + * 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 + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • This resource does not exist.
      • + *
      • This resource is not local.
      • + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @see IResourceDelta#CONTENT + * @see IResourceDelta#DESCRIPTION + */ + public void touch(IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java new file mode 100644 index 0000000000..e5d5d7dc9f --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java @@ -0,0 +1,266 @@ +/******************************************************************************* + * 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; + +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * Resource change events describe changes to resources. + *

      + * There are currently five different types of resource change events: + *

        + *
      • + * Before-the-fact batch reports of arbitrary creations, + * deletions and modifications to one or more resources expressed + * as a hierarchical resource delta. Event type is + * PRE_BUILD, and getDelta returns + * the hierarchical delta rooted at the workspace root. + * The getBuildKind method returns + * the kind of build that is about to occur, and the getSource + * method returns the scope of the build (either the workspace or a single project). + * These events are broadcast to interested parties immediately + * before each build operation. If autobuilding is not enabled, these events still + * occur at times when autobuild would have occurred. The workspace is open + * for change during notification of these events. The delta reported in this event + * cycle is identical across all listeners registered for this type of event. + * Resource changes attempted during a PRE_BUILD callback + * must be done in the thread doing the notification. + *
      • + *
      • + * After-the-fact batch reports of arbitrary creations, + * deletions and modifications to one or more resources expressed + * as a hierarchical resource delta. Event type is + * POST_BUILD, and getDelta returns + * the hierarchical delta rooted at the workspace root. + * The getBuildKind method returns + * the kind of build that occurred, and the getSource + * method returns the scope of the build (either the workspace or a single project). + * These events are broadcast to interested parties at the end of every build operation. + * If autobuilding is not enabled, these events still occur at times when autobuild + * would have occurred. The workspace is open for change during notification of + * these events. The delta reported in this event cycle is identical across + * all listeners registered for this type of event. + * Resource changes attempted during a POST_BUILD callback + * must be done in the thread doing the notification. + *
      • + *
      • + * After-the-fact batch reports of arbitrary creations, + * deletions and modifications to one or more resources expressed + * as a hierarchical resource delta. Event type is + * POST_CHANGE, and getDelta returns + * the hierarchical delta. The resource delta is rooted at the + * workspace root. These events are broadcast to interested parties after + * a set of resource changes and happen whether or not autobuilding is enabled. + * The workspace is closed for change during notification of these events. + * The delta reported in this event cycle is identical across all listeners registered for + * this type of event. + *
      • + *
      • + * Before-the-fact reports of the impending closure of a single + * project. Event type is PRE_CLOSE, + * and getResource returns the project being closed. + * The workspace is closed for change during notification of these events. + *
      • + *
      • + * Before-the-fact reports of the impending deletion of a single + * project. Event type is PRE_DELETE, + * and getResource returns the project being deleted. + * The workspace is closed for change during notification of these events. + *
      • + *
      • + * Before-the-fact reports of the impending refresh of a single project or the workspace. + * Event type is PRE_REFRESH and the getSource + * method returns the scope of the refresh (either the workspace or a single project). + * If the event is fired by a project refresh the getResource + * method returns the project being refreshed. + * The workspace is closed for changes during notification of these events. + *
      • + *
      + *

      + * In order to handle additional event types that may be introduced + * in future releases of the platform, clients should do not write code + * that presumes the set of event types is closed. + *

      + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceChangeEvent { + /** + * Event type constant (bit mask) indicating an after-the-fact + * report of creations, deletions, and modifications + * to one or more resources expressed as a hierarchical + * resource delta as returned by getDelta. + * See class comments for further details. + * + * @see #getType() + * @see #getDelta() + */ + public static final int POST_CHANGE = 1; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of the impending closure of a single + * project as returned by getResource. + * See class comments for further details. + * + * @see #getType() + * @see #getResource() + */ + public static final int PRE_CLOSE = 2; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of the impending deletion of a single + * project as returned by getResource. + * See class comments for further details. + * + * @see #getType() + * @see #getResource() + */ + public static final int PRE_DELETE = 4; + + /** + * @deprecated This event type has been renamed to + * PRE_BUILD + */ + @Deprecated + public static final int PRE_AUTO_BUILD = 8; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of a build. The event contains a hierarchical resource delta + * as returned by getDelta. + * See class comments for further details. + * + * @see #getBuildKind() + * @see #getSource() + * @since 3.0 + */ + public static final int PRE_BUILD = 8; + + /** + * @deprecated This event type has been renamed to + * POST_BUILD + */ + @Deprecated + public static final int POST_AUTO_BUILD = 16; + + /** + * Event type constant (bit mask) indicating an after-the-fact + * report of a build. The event contains a hierarchical resource delta + * as returned by getDelta. + * See class comments for further details. + * + * @see #getBuildKind() + * @see #getSource() + * @since 3.0 + */ + public static final int POST_BUILD = 16; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of refreshing the workspace or a project. + * See class comments for further details. + * + * @see #getType() + * @see #getSource() + * @see #getResource() + * @since 3.4 + */ + public static final int PRE_REFRESH = 32; + + /** + * Returns all marker deltas of the specified type that are associated + * with resource deltas for this event. If includeSubtypes + * is false, only marker deltas whose type exactly matches + * the given type are returned. Returns an empty array if there + * are no matching marker deltas. + *

      + * Calling this method is equivalent to walking the entire resource + * delta for this event, and collecting all marker deltas of a given type. + * The speed of this method will be proportional to the number of changed + * markers, regardless of the size of the resource delta tree. + *

      + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @return an array of marker deltas + * @since 2.0 + */ + public IMarkerDelta[] findMarkerDeltas(String type, boolean includeSubtypes); + + /** + * Returns the kind of build that caused this event, + * or 0 if not applicable to this type of event. + *

      + * If the event is a PRE_BUILD or POST_BUILD + * then this will be the kind of build that occurred to cause the event. + *

      + * + * @see IProject#build(int, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + * @see IncrementalProjectBuilder#AUTO_BUILD + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @return the kind of build, or 0 if not applicable + * @since 3.1 + */ + public int getBuildKind(); + + /** + * Returns a resource delta, rooted at the workspace, describing the set + * of changes that happened to resources in the workspace. + * Returns null if not applicable to this type of event. + * + * @return the resource delta, or null if not + * applicable + */ + public IResourceDelta getDelta(); + + /** + * Returns the resource in question or null + * if not applicable to this type of event. + *

      + * If the event is of type PRE_CLOSE, + * PRE_DELETE, or PRE_REFRESH, then the resource + * will be the affected project. Otherwise the resource will be null. + *

      + * @return the resource, or null if not applicable + */ + public IResource getResource(); + + /** + * Returns an object identifying the source of this event. + *

      + * If the event is a PRE_BUILD, POST_BUILD, + * or PRE_REFRESH then this will be the scope of the build + * (either the {@link IWorkspace} or a single {@link IProject}). + *

      + * + * @return an object identifying the source of this event + * @see java.util.EventObject + */ + public Object getSource(); + + /** + * Returns the type of event being reported. + * + * @return one of the event type constants + * @see #POST_CHANGE + * @see #POST_BUILD + * @see #PRE_BUILD + * @see #PRE_CLOSE + * @see #PRE_DELETE + * @see #PRE_REFRESH + */ + public int getType(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java new file mode 100644 index 0000000000..3776f1218a --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.util.EventListener; + +/** + * A resource change listener is notified of changes to resources + * in the workspace. + * These changes arise from direct manipulation of resources, or + * indirectly through re-synchronization with the local file system. + *

      + * Clients may implement this interface. + *

      + * @see IResourceDelta + * @see IWorkspace#addResourceChangeListener(IResourceChangeListener, int) + */ +public interface IResourceChangeListener extends EventListener { + /** + * Notifies this listener that some resource changes + * are happening, or have already happened. + *

      + * The supplied event gives details. This event object (and the + * resource delta within it) is valid only for the duration of + * the invocation of this method. + *

      + *

      + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

      + * Note that during resource change event notification, further changes + * to resources may be disallowed. + *

      + * + * @param event the resource change event + * @see IResourceDelta + */ + public void resourceChanged(IResourceChangeEvent event); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java new file mode 100644 index 0000000000..2d48b22892 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java @@ -0,0 +1,591 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.internal.watson.IElementComparator; +import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory; +import org.eclipse.core.runtime.*; + +/** + * A resource delta represents changes in the state of a resource tree + * between two discrete points in time. + *

      + * Resource deltas implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

      + * + * @see IResource + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceDelta extends IAdaptable { + + /*==================================================================== + * Constants defining resource delta kinds: + *====================================================================*/ + + /** + * Delta kind constant indicating that the resource has not been changed in any way. + * + * @see IResourceDelta#getKind() + */ + public static final int NO_CHANGE = IElementComparator.K_NO_CHANGE; + + /** + * Delta kind constant (bit mask) indicating that the resource has been added + * to its parent. That is, one that appears in the "after" state, + * not in the "before" one. + * + * @see IResourceDelta#getKind() + */ + public static final int ADDED = 0x1; + + /** + * Delta kind constant (bit mask) indicating that the resource has been removed + * from its parent. That is, one that appears in the "before" state, + * not in the "after" one. + * + * @see IResourceDelta#getKind() + */ + public static final int REMOVED = 0x2; + + /** + * Delta kind constant (bit mask) indicating that the resource has been changed. + * That is, one that appears in both the "before" and "after" states. + * + * @see IResourceDelta#getKind() + */ + public static final int CHANGED = 0x4; + + /** + * Delta kind constant (bit mask) indicating that a phantom resource has been added at + * the location of the delta node. + * + * @see IResourceDelta#getKind() + */ + public static final int ADDED_PHANTOM = 0x8; + + /** + * Delta kind constant (bit mask) indicating that a phantom resource has been removed from + * the location of the delta node. + * + * @see IResourceDelta#getKind() + */ + public static final int REMOVED_PHANTOM = 0x10; + + /** + * The bit mask which describes all possible delta kinds, + * including ones involving phantoms. + * + * @see IResourceDelta#getKind() + */ + public static final int ALL_WITH_PHANTOMS = CHANGED | ADDED | REMOVED | ADDED_PHANTOM | REMOVED_PHANTOM; + + /*==================================================================== + * Constants which describe resource changes: + *====================================================================*/ + + /** + * Change constant (bit mask) indicating that the content of the resource has changed. + * + * @see IResourceDelta#getFlags() + */ + public static final int CONTENT = 0x100; + + /** + * Change constant (bit mask) indicating that the resource was moved from another location. + * The location in the "before" state can be retrieved using getMovedFromPath(). + * + * @see IResourceDelta#getFlags() + */ + public static final int MOVED_FROM = 0x1000; + + /** + * Change constant (bit mask) indicating that the resource was moved to another location. + * The location in the new state can be retrieved using getMovedToPath(). + * + * @see IResourceDelta#getFlags() + */ + public static final int MOVED_TO = 0x2000; + + /** + * Change constant (bit mask) indicating that the resource was copied from another location. + * The location in the "before" state can be retrieved using getMovedFromPath(). + * This flag is only used when describing potential changes using an {@link IResourceChangeDescriptionFactory}. + * + * @see IResourceDelta#getFlags() + * @since 3.2 + */ + public static final int COPIED_FROM = 0x800; + /** + * Change constant (bit mask) indicating that the resource was opened or closed. + * This flag is also set when the project did not exist in the "before" state. + * For example, if the current state of the resource is open then it was previously closed + * or did not exist. + * + * @see IResourceDelta#getFlags() + */ + public static final int OPEN = 0x4000; + + /** + * Change constant (bit mask) indicating that the type of the resource has changed. + * + * @see IResourceDelta#getFlags() + */ + public static final int TYPE = 0x8000; + + /** + * Change constant (bit mask) indicating that the resource's sync status has changed. + * This type of change is not included in build deltas, only in those for resource notification. + * + * @see IResourceDelta#getFlags() + */ + public static final int SYNC = 0x10000; + + /** + * Change constant (bit mask) indicating that the resource's markers have changed. + * This type of change is not included in build deltas, only in those for resource notification. + * + * @see IResourceDelta#getFlags() + */ + public static final int MARKERS = 0x20000; + + /** + * Change constant (bit mask) indicating that the resource has been + * replaced by another at the same location (i.e., the resource has + * been deleted and then added). + * + * @see IResourceDelta#getFlags() + */ + public static final int REPLACED = 0x40000; + + /** + * Change constant (bit mask) indicating that a project's description has changed. + * + * @see IResourceDelta#getFlags() + */ + public static final int DESCRIPTION = 0x80000; + + /** + * Change constant (bit mask) indicating that the encoding of the resource has changed. + * + * @see IResourceDelta#getFlags() + * @since 3.0 + */ + public static final int ENCODING = 0x100000; + + /** + * Change constant (bit mask) indicating that the underlying file or folder of the linked resource has been added or removed. + * + * @see IResourceDelta#getFlags() + * @since 3.4 + */ + public static final int LOCAL_CHANGED = 0x200000; + + /** + * Change constant (bit mask) indicating that the derived flag of the resource has changed. + * + * @see IResourceDelta#getFlags() + * @since 3.6 + */ + public static final int DERIVED_CHANGED = 0x400000; + + /** + * Accepts the given visitor. + * The only kinds of resource deltas visited + * are ADDED, REMOVED, + * and CHANGED. + * The visitor's visit method is called with this + * resource delta if applicable. If the visitor returns true, + * the resource delta's children are also visited. + *

      + * This is a convenience method, fully equivalent to + * accept(visitor, IResource.NONE). + * Although the visitor will be invoked for this resource delta, it will not be + * invoked for any team-private member resources. + *

      + * + * @param visitor the visitor + * @exception CoreException if the visitor failed with this exception. + * @see IResourceDeltaVisitor#visit(IResourceDelta) + */ + public void accept(IResourceDeltaVisitor visitor) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource delta. If the visitor returns true, + * the resource delta's children are also visited. + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   accept(visitor, includePhantoms ? INCLUDE_PHANTOMS : IResource.NONE);
      +	 * 
      + * Although the visitor will be invoked for this resource delta, it will not be + * invoked for any team-private member resources. + *

      + * + * @param visitor the visitor + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @exception CoreException if the visitor failed with this exception. + * @see #accept(IResourceDeltaVisitor) + * @see IResource#isPhantom() + * @see IResourceDeltaVisitor#visit(IResourceDelta) + */ + public void accept(IResourceDeltaVisitor visitor, boolean includePhantoms) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource delta. If the visitor returns true, + * the resource delta's children are also visited. + *

      + * The member flags determine which child deltas of this resource delta will be visited. + * The visitor will always be invoked for this resource delta. + *

      + * If the INCLUDE_PHANTOMS member flag is not specified + * (recommended), only child resource deltas involving existing resources will be visited + * (kinds ADDED, REMOVED, and CHANGED). + * If the INCLUDE_PHANTOMS member flag is specified, + * the result will also include additions and removes of phantom resources + * (kinds ADDED_PHANTOM and REMOVED_PHANTOM). + *

      + *

      + * If the INCLUDE_TEAM_PRIVATE_MEMBERS member flag is not specified + * (recommended), resource deltas involving team private member resources will be + * excluded from the visit. If the INCLUDE_TEAM_PRIVATE_MEMBERS member + * flag is specified, the visit will also include additions and removes of + * team private member resources. + *

      + * + * @param visitor the visitor + * @param memberFlags bit-wise or of member flag constants + * (IContainer.INCLUDE_PHANTOMS, INCLUDE_HIDDEN + * and INCLUDE_TEAM_PRIVATE_MEMBERS) indicating which members are of interest + * @exception CoreException if the visitor failed with this exception. + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResource#isHidden() + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IResourceDeltaVisitor#visit(IResourceDelta) + * @since 2.0 + */ + public void accept(IResourceDeltaVisitor visitor, int memberFlags) throws CoreException; + + /** + * Finds and returns the descendent delta identified by the given path in + * this delta, or null if no such descendent exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this delta. Trailing separators are ignored. + * If the path is empty this delta is returned. + *

      + * This is a convenience method to avoid manual traversal of the delta + * tree in cases where the listener is only interested in changes to + * particular resources. Calling this method will generally be + * faster than manually traversing the delta to a particular descendent. + *

      + * @param path the path of the desired descendent delta + * @return the descendent delta, or null if no such + * descendent exists in the delta + * @since 2.0 + */ + public IResourceDelta findMember(IPath path); + + /** + * Returns resource deltas for all children of this resource + * which were added, removed, or changed. Returns an empty + * array if there are no affected children. + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   getAffectedChildren(ADDED | REMOVED | CHANGED, IResource.NONE);
      +	 * 
      + * Team-private member resources are not included in the result; neither are + * phantom resources. + *

      + * + * @return the resource deltas for all affected children + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see #getAffectedChildren(int,int) + */ + public IResourceDelta[] getAffectedChildren(); + + /** + * Returns resource deltas for all children of this resource + * whose kind is included in the given mask. Kind masks are formed + * by the bitwise or of IResourceDelta kind constants. + * Returns an empty array if there are no affected children. + *

      + * This is a convenience method, fully equivalent to: + *

      +	 *   getAffectedChildren(kindMask, IResource.NONE);
      +	 * 
      + * Team-private member resources are not included in the result. + *

      + * + * @param kindMask a mask formed by the bitwise or of IResourceDelta + * delta kind constants + * @return the resource deltas for all affected children + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see IResourceDelta#ADDED_PHANTOM + * @see IResourceDelta#REMOVED_PHANTOM + * @see IResourceDelta#ALL_WITH_PHANTOMS + * @see #getAffectedChildren(int,int) + */ + public IResourceDelta[] getAffectedChildren(int kindMask); + + /** + * Returns resource deltas for all children of this resource + * whose kind is included in the given mask. Masks are formed + * by the bitwise or of IResourceDelta kind constants. + * Returns an empty array if there are no affected children. + *

      + * If the INCLUDE_TEAM_PRIVATE_MEMBERS member flag is not specified, + * (recommended), resource deltas involving team private member resources will be + * excluded. If the INCLUDE_TEAM_PRIVATE_MEMBERS member + * flag is specified, the result will also include resource deltas of the + * specified kinds to team private member resources. + *

      + *

      + * If the {@link IContainer#INCLUDE_HIDDEN} member flag is not specified, + * (recommended), resource deltas involving hidden resources will be + * excluded. If the {@link IContainer#INCLUDE_HIDDEN} member + * flag is specified, the result will also include resource deltas of the + * specified kinds to hidden resources. + *

      + *

      + * Specifying the IContainer.INCLUDE_PHANTOMS member flag is equivalent + * to including IContainer.ADDED_PHANTOM and IContainer.REMOVED_PHANTOM + * in the kind mask. + *

      + * + * @param kindMask a mask formed by the bitwise or of IResourceDelta + * delta kind constants + * @param memberFlags bit-wise or of member flag constants + * (IContainer.INCLUDE_PHANTOMS, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS + * and IContainer.INCLUDE_HIDDEN) + * indicating which members are of interest + * @return the resource deltas for all affected children + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see IResourceDelta#ADDED_PHANTOM + * @see IResourceDelta#REMOVED_PHANTOM + * @see IResourceDelta#ALL_WITH_PHANTOMS + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @since 2.0 + */ + public IResourceDelta[] getAffectedChildren(int kindMask, int memberFlags); + + /** + * Returns flags which describe in more detail how a resource has been affected. + *

      + * The following codes (bit masks) are used when kind is CHANGED, and + * also when the resource is involved in a move: + *

        + *
      • CONTENT - The bytes contained by the resource have + * been altered, or IResource.touch has been called on + * the resource.
      • + *
      • DERIVED_CHANGED - The derived flag of the resource has + * been altered.
      • + *
      • ENCODING - The encoding of the resource may have been altered. + * This flag is not set when the encoding changes due to the file being modified, + * or being moved.
      • + *
      • DESCRIPTION - The description of the project has been altered, + * or IResource.touch has been called on the project. + * This flag is only valid for project resources.
      • + *
      • OPEN - The project's open/closed state has changed. + * If it is not open, it was closed, and vice versa. This flag is only valid for project resources.
      • + *
      • TYPE - The resource (a folder or file) has changed its type.
      • + *
      • SYNC - The resource's sync status has changed.
      • + *
      • MARKERS - The resource's markers have changed.
      • + *
      • REPLACED - The resource (and all its properties) + * was deleted (either by a delete or move), and was subsequently re-created + * (either by a create, move, or copy).
      • + *
      • LOCAL_CHANGED - The resource is a linked resource, + * and the underlying file system object has been added or removed.
      • + *
      + * The following code is only used if kind is REMOVED + * (or CHANGED in conjunction with REPLACED): + *
        + *
      • MOVED_TO - The resource has moved. + * getMovedToPath will return the path of where it was moved to.
      • + *
      + * The following code is only used if kind is ADDED + * (or CHANGED in conjunction with REPLACED): + *
        + *
      • MOVED_FROM - The resource has moved. + * getMovedFromPath will return the path of where it was moved from.
      • + *
      + * The following code is only used when describing potential changes using an {@link IResourceChangeDescriptionFactory}: + *
        + *
      • COPIED_FROM - Change constant (bit mask) indicating that the resource was copied from another location. + * The location in the "before" state can be retrieved using getMovedFromPath().
      • + *
      + * + * A simple move operation would result in the following delta information. + * If a resource is moved from A to B (with no other changes to A or B), + * then A will have kind REMOVED, with flag MOVED_TO, + * and getMovedToPath on A will return the path for B. + * B will have kind ADDED, with flag MOVED_FROM, + * and getMovedFromPath on B will return the path for A. + * B's other flags will describe any other changes to the resource, as compared + * to its previous location at A. + *

      + *

      + * Note that the move flags only describe the changes to a single resource; they + * don't necessarily imply anything about the parent or children of the resource. + * If the children were moved as a consequence of a subtree move operation, + * they will have corresponding move flags as well. + *

      + *

      + * Note that it is possible for a file resource to be replaced in the workspace + * by a folder resource (or the other way around). + * The resource delta, which is actually expressed in terms of + * paths instead or resources, shows this as a change to either the + * content or children. + *

      + * + * @return the flags + * @see IResourceDelta#CONTENT + * @see IResourceDelta#DERIVED_CHANGED + * @see IResourceDelta#DESCRIPTION + * @see IResourceDelta#ENCODING + * @see IResourceDelta#LOCAL_CHANGED + * @see IResourceDelta#OPEN + * @see IResourceDelta#MOVED_TO + * @see IResourceDelta#MOVED_FROM + * @see IResourceDelta#COPIED_FROM + * @see IResourceDelta#TYPE + * @see IResourceDelta#SYNC + * @see IResourceDelta#MARKERS + * @see IResourceDelta#REPLACED + * @see #getKind() + * @see #getMovedFromPath() + * @see #getMovedToPath() + * @see IResource#move(IPath, int, IProgressMonitor) + */ + public int getFlags(); + + /** + * Returns the full, absolute path of this resource delta. + *

      + * Note: the returned path never has a trailing separator. + *

      + * @return the full, absolute path of this resource delta + * @see IResource#getFullPath() + * @see #getProjectRelativePath() + */ + public IPath getFullPath(); + + /** + * Returns the kind of this resource delta. + * Normally, one of ADDED, + * REMOVED, CHANGED. + * When phantom resources have been explicitly requested, + * there are two additional kinds: ADDED_PHANTOM + * and REMOVED_PHANTOM. + * + * @return the kind of this resource delta + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see IResourceDelta#ADDED_PHANTOM + * @see IResourceDelta#REMOVED_PHANTOM + */ + public int getKind(); + + /** + * Returns the changes to markers on the corresponding resource. + * Returns an empty array if no markers changed. + * + * @return the marker deltas + */ + public IMarkerDelta[] getMarkerDeltas(); + + /** + * Returns the full path (in the "before" state) from which this resource + * (in the "after" state) was moved. This value is only valid + * if the MOVED_FROM change flag is set; otherwise, + * null is returned. + *

      + * Note: the returned path never has a trailing separator. + * + * @return a path, or null + * @see #getMovedToPath() + * @see #getFullPath() + * @see #getFlags() + */ + public IPath getMovedFromPath(); + + /** + * Returns the full path (in the "after" state) to which this resource + * (in the "before" state) was moved. This value is only valid if the + * MOVED_TO change flag is set; otherwise, + * null is returned. + *

      + * Note: the returned path never has a trailing separator. + * + * @return a path, or null + * @see #getMovedFromPath() + * @see #getFullPath() + * @see #getFlags() + */ + public IPath getMovedToPath(); + + /** + * Returns the project-relative path of this resource delta. + * Returns the empty path for projects and the workspace root. + *

      + * A resource's project-relative path indicates the route from the project + * to the resource. Within a workspace, there is exactly one such path + * for any given resource. The returned path never has a trailing separator. + *

      + * @return the project-relative path of this resource delta + * @see IResource#getProjectRelativePath() + * @see #getFullPath() + * @see Path#EMPTY + */ + public IPath getProjectRelativePath(); + + /** + * Returns a handle for the affected resource. + *

      + * For additions (ADDED), this handle describes the newly-added resource; i.e., + * the one in the "after" state. + *

      + * For changes (CHANGED), this handle also describes the resource in the "after" + * state. When a file or folder resource has changed type, the + * former type of the handle can be inferred. + *

      + * For removals (REMOVED), this handle describes the resource in the "before" + * state. Even though this resource would not normally exist in the + * current workspace, the type of resource that was removed can be + * determined from the handle. + *

      + * For phantom additions and removals (ADDED_PHANTOM + * and REMOVED_PHANTOM), this is the handle of the phantom resource. + * + * @return the affected resource (handle) + */ + public IResource getResource(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java new file mode 100644 index 0000000000..87750a98d6 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * An objects that visits resource deltas. + *

      + * Usage: + *

      + * class Visitor implements IResourceDeltaVisitor {
      + *     public boolean visit(IResourceDelta delta) {
      + *         switch (delta.getKind()) {
      + *         case IResourceDelta.ADDED :
      + *             // handle added resource
      + *             break;
      + *         case IResourceDelta.REMOVED :
      + *             // handle removed resource
      + *             break;
      + *         case IResourceDelta.CHANGED :
      + *             // handle changed resource
      + *             break;
      + *         }
      + *     return true;
      + *     }
      + * }
      + * IResourceDelta rootDelta = ...;
      + * rootDelta.accept(new Visitor());
      + * 
      + *

      + *

      + * Clients may implement this interface. + *

      + * + * @see IResource#accept(IResourceVisitor) + */ +public interface IResourceDeltaVisitor { + /** + * Visits the given resource delta. + * + * @return true if the resource delta's children should + * be visited; false if they should be skipped. + * @exception CoreException if the visit fails for some reason. + */ + public boolean visit(IResourceDelta delta) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java new file mode 100644 index 0000000000..43a8d67a03 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 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 - ongoing development + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.runtime.*; + +/** + * A description of a resource filter. + * + * A filter determines which file system objects will be visible when a local refresh is + * performed for an IContainer. + * + * @see IContainer#getFilters() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * @since 3.6 + */ +public interface IResourceFilterDescription { + + /*==================================================================== + * Constants defining which members are wanted: + *====================================================================*/ + + /** + * Flag for resource filters indicating that the filter list includes only + * the files matching the filters. All INCLUDE_ONLY filters are applied to + * the resource list with an logical OR operation. + */ + public static final int INCLUDE_ONLY = 1; + + /** + * Flag for resource filters indicating that the filter list excludes all + * the files matching the filters. All EXCLUDE_ALL filters are applied to + * the resource list with an logical AND operation. + */ + public static final int EXCLUDE_ALL = 2; + + /** + * Flag for resource filters indicating that this filter applies to files. + */ + public static final int FILES = 4; + + /** + * Flag for resource filters indicating that this filter applies to folders. + */ + public static final int FOLDERS = 8; + + /** + * Flag for resource filters indicating that the container children of the + * path inherit from this filter as well. + */ + public static final int INHERITABLE = 16; + + /** + * Returns the description of the file info matcher corresponding to this resource + * filter. + * @return the file info matcher description for this resource filter + */ + public FileInfoMatcherDescription getFileInfoMatcherDescription(); + + /** + * Return the resource towards which this filter is set. + * + * @return the resource towards which this filter is set + */ + public IResource getResource(); + + /** + * Return the filter type, either INCLUDE_ONLY or EXCLUDE_ALL + * + * @return (INCLUDE_ONLY or EXCLUDE_ALL) and/or INHERITABLE + */ + public int getType(); + + /** + * Deletes this filter description from its associated resource. + *

      + * The {@link IResource#BACKGROUND_REFRESH} update flag controls when + * changes to the resource hierarchy under this container resulting from the filter + * removal take effect. If this flag is specified, the resource hierarchy is updated in a + * separate thread after this method returns. If the flag is not specified, any resource + * changes resulting from the filter removal will occur before this method returns. + *

      + *

      + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include an indication + * of any resources that have been added as a result of the filter removal. + *

      + *

      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

      + * + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#BACKGROUND_REFRESH}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this filter could not be removed. Reasons include: + *
        + *
      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IContainer#getFilters() + * @see IContainer#createFilter(int, FileInfoMatcherDescription, int, IProgressMonitor) + */ + public void delete(int updateFlags, IProgressMonitor monitor) throws CoreException; + +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java new file mode 100644 index 0000000000..54a2e7cca4 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.QualifiedName; + +/** + * A lightweight interface for requesting information about a resource. + * All of the "get" methods on a resource proxy have trivial performance cost. + * Requesting the full path or the actual resource handle will cause extra objects + * to be created and will thus have greater cost. + *

      + * When a resource proxy is used within an {@link IResourceProxyVisitor}, + * it is a transient object that is only valid for the duration of a single visit method. + * A proxy should not be referenced once the single resource visit is complete. + * The equals and hashCode methods should not be relied on. + *

      + *

      + * A proxy can also be created using {@link IResource#createProxy()}. In + * this case the proxy is valid indefinitely, but will not remain in sync with + * the state of the corresponding resource. + *

      + * + * @see IResourceProxyVisitor + * @since 2.1 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceProxy { + /** + * Returns the modification stamp of the resource being visited. + * + * @return the modification stamp, or NULL_STAMP if the + * resource either does not exist or exists as a closed project + * @see IResource#getModificationStamp() + */ + public long getModificationStamp(); + + /** + * Returns whether the resource being visited is accessible. + * + * @return true if the resource is accessible, and + * false otherwise + * @see IResource#isAccessible() + */ + public boolean isAccessible(); + + /** + * Returns whether the resource being visited is derived. + * + * @return true if the resource is marked as derived, and + * false otherwise + * @see IResource#isDerived() + */ + public boolean isDerived(); + + /** + * Returns whether the resource being visited is a linked resource. + * + * @return true if the resource is linked, and + * false otherwise + * @see IResource#isLinked() + */ + public boolean isLinked(); + + /** + * Returns whether the resource being visited is a phantom resource. + * + * @return true if the resource is a phantom resource, and + * false otherwise + * @see IResource#isPhantom() + */ + public boolean isPhantom(); + + /** + * Returns whether the resource being visited is a hidden resource. + * + * @return true if the resource is a hidden resource, and + * false otherwise + * @see IResource#isHidden() + * + * @since 3.4 + */ + public boolean isHidden(); + + /** + * Returns whether the resource being visited is a team private member. + * + * @return true if the resource is a team private member, and + * false otherwise + * @see IResource#isTeamPrivateMember() + */ + public boolean isTeamPrivateMember(); + + /** + * Returns the simple name of the resource being visited. + * + * @return the name of the resource + * @see IResource#getName() + */ + public String getName(); + + /** + * Returns the value of the session property of the resource being + * visited, identified by the given key. Returns null if this + * resource has no such property. + *

      + * Note that this method can return an out of date property value, or a + * value that no longer exists, if session properties are being modified + * concurrently with the resource visit. + *

      + * + * @param key the qualified name of the property + * @return the string value of the session property, + * or null if the resource has no such property + * @see IResource#getSessionProperty(QualifiedName) + */ + public Object getSessionProperty(QualifiedName key); + + /** + * Returns the type of the resource being visited. + * + * @return the resource type + * @see IResource#getType() + */ + public int getType(); + + /** + * Returns the full workspace path of the resource being visited. + *

      + * Note that this is not a "free" proxy operation. This method + * will generally cause a path object to be created. For an optimal + * visitor, only call this method when absolutely necessary. Note that the + * simple resource name can be obtained from the proxy with no cost. + *

      + * @return the full path of the resource + * @see IResource#getFullPath() + */ + public IPath requestFullPath(); + + /** + * Returns the handle of the resource being visited. + *

      + * Note that this is not a "free" proxy operation. This method will + * generally cause both a path object and a resource object to be created. + * For an optimal visitor, only call this method when absolutely necessary. + * Note that the simple resource name can be obtained from the proxy with no + * cost, and the full path of the resource can be obtained through the proxy + * with smaller cost. + *

      + * @return the resource handle + */ + public IResource requestResource(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java new file mode 100644 index 0000000000..851bd0ce7f --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * This interface is implemented by objects that visit resource trees. The fast + * visitor is an optimized mechanism for tree traversal that creates a minimal + * number of objects. The visitor is provided with a callback interface, + * instead of a resource. Through the callback, the visitor can request + * information about the resource being visited. + *

      + * Usage: + *

      + * class Visitor implements IResourceProxyVisitor { 	
      + * 	public boolean visit (IResourceProxy proxy) { 
      + * 		//	 your code here 
      + * 		return true;  
      + * 	} 
      + * } 
      + * ResourcesPlugin.getWorkspace().getRoot().accept(new Visitor(), IResource.NONE);
      + * 
      + *

      + *

      + * Clients may implement this interface. + *

      + * + * @see IResource#accept(IResourceVisitor) + * @since 2.1 + */ +public interface IResourceProxyVisitor { + /** + * Visits the given resource. + * + * @param proxy for requesting information about the resource being visited; + * this object is only valid for the duration of the invocation of this + * method, and must not be used after this method has completed + * @return true if the resource's members should + * be visited; false if they should be skipped + * @exception CoreException if the visit fails for some reason. + */ + public boolean visit(IResourceProxy proxy) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java new file mode 100644 index 0000000000..4007a6f9c7 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 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 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.QualifiedName; + +/** + * A resource rule factory returns scheduling rules for API methods + * that modify the workspace. These rules can be used when creating jobs + * or other operations that perform a series of modifications on the workspace. + * This allows clients to implement two phase commit semantics, where all + * necessary rules are obtained prior to executing a long running operation. + *

      + * Note that simple use of the workspace APIs does not require use of scheduling + * rules. All workspace API methods that modify the workspace will automatically + * obtain any scheduling rules needed to perform the modification. However, if you + * are aggregating a set of changes to the workspace using WorkspaceJob + * or IWorkspaceRunnable you can use scheduling rules to lock a + * portion of the workspace for the duration of the job or runnable. If you + * provide a non-null scheduling rule, a runtime exception will occur if you try to + * modify a portion of the workspace that is not covered by the rule for the runnable or job. + *

      + * If more than one rule is needed, they can be aggregated using the + * MultiRule.combine method. Simplifying a group of rules does not change + * the set of resources that are covered, but can improve job scheduling performance. + *

      + * Note that null is a valid scheduling rule (indicating that no + * resources need to be locked), and thus all methods in this class may + * return null. + * + * @see WorkspaceJob + * @see IWorkspace#run(IWorkspaceRunnable, ISchedulingRule, int, org.eclipse.core.runtime.IProgressMonitor) + * @see org.eclipse.core.runtime.jobs.MultiRule#combine(ISchedulingRule, ISchedulingRule) + * @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 IResourceRuleFactory { + /** + * Returns the scheduling rule that is required for creating a project, folder, + * or file. + * + * @param resource the resource being created + * @return a scheduling rule, or null + */ + public ISchedulingRule createRule(IResource resource); + + /** + * Returns the scheduling rule that is required for building a project or the + * entire workspace. + * + * @return a scheduling rule, or null + */ + public ISchedulingRule buildRule(); + + /** + * Returns the scheduling rule that is required for changing the charset + * setting for a file or the default charset setting for a container. + * + * @param resource the resource for which the charset will be changed + * @return a scheduling rule, or null + * @since 3.1 + */ + public ISchedulingRule charsetRule(IResource resource); + + /** + * Returns the scheduling rule that is required for changing the derived flag + * on a resource. + * + * @param resource the resource for which the derived flag will be changed + * @return a scheduling rule, or null + * @since 3.6 + */ + public ISchedulingRule derivedRule(IResource resource); + + /** + * Returns the scheduling rule that is required for copying a resource. + * + * @param source the source of the copy + * @param destination the destination of the copy + * @return a scheduling rule, or null + */ + public ISchedulingRule copyRule(IResource source, IResource destination); + + /** + * Returns the scheduling rule that is required for deleting a resource. + * + * @param resource the resource to be deleted + * @return a scheduling rule, or null + */ + public ISchedulingRule deleteRule(IResource resource); + + /** + * Returns the scheduling rule that is required for creating, modifying, or + * deleting markers on a resource. + * + * @param resource the resource owning the marker to be modified + * @return a scheduling rule, or null + */ + public ISchedulingRule markerRule(IResource resource); + + /** + * Returns the scheduling rule that is required for maintaining sync + * info of a resource. + * + * @see ISynchronizer#setSyncInfo(QualifiedName, IResource, byte[]) + * @param resource the resource related to the sync info + * @return a scheduling rule, or null + * @since 3.12.100 + */ + public ISchedulingRule syncInfoRule(IResource resource); + + /** + * Returns the scheduling rule that is required for modifying a resource. + * For files, modification includes setting and appending contents. For + * projects, modification includes opening or closing the project, or + * setting the project description using the + * {@link IResource#AVOID_NATURE_CONFIG} flag. For all resources + * touch is considered to be a modification. + * + * @param resource the resource being modified + * @return a scheduling rule, or null + */ + public ISchedulingRule modifyRule(IResource resource); + + /** + * Returns the scheduling rule that is required for moving a resource. + * + * @param source the source of the move + * @param destination the destination of the move + * @return a scheduling rule, or null + */ + public ISchedulingRule moveRule(IResource source, IResource destination); + + /** + * Returns the scheduling rule that is required for performing + * refreshLocal on a resource. + * + * @param resource the resource to refresh + * @return a scheduling rule, or null + */ + public ISchedulingRule refreshRule(IResource resource); + + /** + * Returns the scheduling rule that is required for a validateEdit + * + * @param resources the resources to be validated + * @return a scheduling rule, or null + */ + public ISchedulingRule validateEditRule(IResource[] resources); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java new file mode 100644 index 0000000000..7b8ca6bbe4 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java @@ -0,0 +1,322 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.runtime.*; + +/** + * Represents status related to resources in the Resources plug-in and + * defines the relevant status code constants. + * Status objects created by the Resources plug-in bear its unique id + * (ResourcesPlugin.PI_RESOURCES) and one of + * these status codes. + * + * @see org.eclipse.core.runtime.IStatus + * @see ResourcesPlugin#PI_RESOURCES + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceStatus extends IStatus { + + /* + * Status code definitions + */ + + // General constants [0-98] + // Information Only [0-32] + // Warnings [33-65] + /** Status code constant (value 35) indicating that a given + * nature set does not satisfy its constraints. + * Severity: warning. Category: general. + */ + public static final int INVALID_NATURE_SET = 35; + + // Errors [66-98] + + /** Status code constant (value 75) indicating that a builder failed. + * Severity: error. Category: general. + */ + public static final int BUILD_FAILED = 75; + + /** Status code constant (value 76) indicating that an operation failed. + * Severity: error. Category: general. + */ + public static final int OPERATION_FAILED = 76; + + /** Status code constant (value 77) indicating an invalid value. + * Severity: error. Category: general. + */ + public static final int INVALID_VALUE = 77; + + // Local file system constants [200-298] + // Information Only [200-232] + + // Warnings [233-265] + + /** Status code constant (value 234) indicating that a project + * description file (.project), was missing but it has been repaired. + * Severity: warning. Category: local file system. + */ + public static final int MISSING_DESCRIPTION_REPAIRED = 234; + + /** Status code constant (value 235) indicating the local file system location + * for a resource overlaps the location of another resource. + * Severity: warning. Category: local file system. + */ + public static final int OVERLAPPING_LOCATION = 235; + + // Errors [266-298] + + /** Status code constant (value 268) indicating a resource unexpectedly + * exists on the local file system. + * Severity: error. Category: local file system. + */ + public static final int EXISTS_LOCAL = 268; + + /** Status code constant (value 269) indicating a resource unexpectedly + * does not exist on the local file system. + * Severity: error. Category: local file system. + */ + public static final int NOT_FOUND_LOCAL = 269; + + /** Status code constant (value 270) indicating the local file system location for + * a resource could not be computed. + * Severity: error. Category: local file system. + */ + public static final int NO_LOCATION_LOCAL = 270; + + /** Status code constant (value 271) indicating an error occurred while + * reading part of a resource from the local file system. + * Severity: error. Category: local file system. + */ + public static final int FAILED_READ_LOCAL = 271; + + /** Status code constant (value 272) indicating an error occurred while + * writing part of a resource to the local file system. + * Severity: error. Category: local file system. + */ + public static final int FAILED_WRITE_LOCAL = 272; + + /** Status code constant (value 273) indicating an error occurred while + * deleting a resource from the local file system. + * Severity: error. Category: local file system. + */ + public static final int FAILED_DELETE_LOCAL = 273; + + /** Status code constant (value 274) indicating the workspace view of + * the resource differs from that of the local file system. The requested + * operation has been aborted to prevent the possible loss of data. + * Severity: error. Category: local file system. + */ + public static final int OUT_OF_SYNC_LOCAL = 274; + + /** Status code constant (value 275) indicating this file system is not case + * sensitive and a resource that differs only in case unexpectedly exists on + * the local file system. + * Severity: error. Category: local file system. + */ + public static final int CASE_VARIANT_EXISTS = 275; + + /** Status code constant (value 276) indicating a file exists in the + * file system but is not of the expected type (file instead of directory, + * or vice-versa). + * Severity: error. Category: local file system. + */ + public static final int WRONG_TYPE_LOCAL = 276; + + /** Status code constant (value 277) indicating that the parent + * file in the file system is marked as read-only. + * Severity: error. Category: local file system. + * @since 2.1 + */ + public static final int PARENT_READ_ONLY = 277; + + /** Status code constant (value 278) indicating a file exists in the + * file system but its name is not a valid resource name. + * Severity: error. Category: local file system. + */ + public static final int INVALID_RESOURCE_NAME = 278; + + /** Status code constant (value 279) indicating that the + * file in the file system is marked as read-only. + * Severity: error. Category: local file system. + * @since 3.0 + */ + public static final int READ_ONLY_LOCAL = 279; + + // Workspace constants [300-398] + // Information Only [300-332] + + // Warnings [333-365] + + /** Status code constant (value 333) indicating that a workspace path + * variable unexpectedly does not exist. + * Severity: warning. Category: workspace. + * @since 2.1 + */ + public static final int VARIABLE_NOT_DEFINED_WARNING = 333; + + // Errors [366-398] + + /** Status code constant (value 366) indicating a resource exists in the + * workspace but is not of the expected type. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_WRONG_TYPE = 366; + + /** Status code constant (value 367) indicating a resource unexpectedly + * exists in the workspace. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_EXISTS = 367; + + /** Status code constant (value 368) indicating a resource unexpectedly + * does not exist in the workspace. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_NOT_FOUND = 368; + + /** Status code constant (value 369) indicating a resource unexpectedly + * does not have content local to the workspace. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_NOT_LOCAL = 369; + + /** Status code constant (value 370) indicating a workspace + * is unexpectedly closed. + * Severity: error. Category: workspace. + */ + public static final int WORKSPACE_NOT_OPEN = 370; + + /** Status code constant (value 372) indicating a project is + * unexpectedly closed. + * Severity: error. Category: workspace. + */ + public static final int PROJECT_NOT_OPEN = 372; + + /** Status code constant (value 374) indicating that the path + * of a resource being created is occupied by an existing resource + * of a different type. + * Severity: error. Category: workspace. + */ + public static final int PATH_OCCUPIED = 374; + + /** Status code constant (value 375) indicating that the sync partner + * is not registered with the workspace synchronizer. + * Severity: error. Category: workspace. + */ + public static final int PARTNER_NOT_REGISTERED = 375; + + /** Status code constant (value 376) indicating a marker unexpectedly + * does not exist in the workspace tree. + * Severity: error. Category: workspace. + */ + public static final int MARKER_NOT_FOUND = 376; + + /** Status code constant (value 377) indicating a resource is + * unexpectedly not a linked resource. + * Severity: error. Category: workspace. + * @since 2.1 + */ + public static final int RESOURCE_NOT_LINKED = 377; + + /** Status code constant (value 378) indicating that linking is + * not permitted on a certain project. + * Severity: error. Category: workspace. + * @since 2.1 + */ + public static final int LINKING_NOT_ALLOWED = 378; + + /** Status code constant (value 379) indicating that a workspace path + * variable unexpectedly does not exist. + * Severity: error. Category: workspace. + * @since 2.1 + */ + public static final int VARIABLE_NOT_DEFINED = 379; + + /** Status code constant (value 380) indicating that an attempt was made to modify + * the workspace while it was locked. Resource changes are disallowed + * during certain types of resource change event notification. + * Severity: error. Category: workspace. + * @see IResourceChangeEvent + * @since 2.1 + */ + public static final int WORKSPACE_LOCKED = 380; + + /** Status code constant (value 381) indicating that a problem occurred while + * retrieving the content description for a resource. + * Severity: error. Category: workspace. + * @see IFile#getContentDescription + * @since 3.0 + */ + public static final int FAILED_DESCRIBING_CONTENTS = 381; + + /** Status code constant (value 382) indicating that a problem occurred while + * setting the charset for a resource. + * Severity: error. Category: workspace. + * @see IContainer#setDefaultCharset(String, IProgressMonitor) + * @see IFile#setCharset(String, IProgressMonitor) + * @since 3.0 + */ + public static final int FAILED_SETTING_CHARSET = 382; + + /** Status code constant (value 383) indicating that a problem occurred while + * getting the charset for a resource. + * Severity: error. Category: workspace. + * @since 3.0 + */ + public static final int FAILED_GETTING_CHARSET = 383; + + /** Status code constant (value 384) indicating a build configuration with + * the specified ID unexpectedly does not exist. + * Severity: error. Category: workspace. + * @since 3.7 + */ + public static final int BUILD_CONFIGURATION_NOT_FOUND = 384; + + // Internal constants [500-598] + // Information Only [500-532] + + // Warnings [533-565] + + // Errors [566-598] + + /** Status code constant (value 566) indicating an error internal to the + * platform has occurred. + * Severity: error. Category: internal. + */ + public static final int INTERNAL_ERROR = 566; + + /** Status code constant (value 567) indicating the platform could not read + * some of its metadata. + * Severity: error. Category: internal. + */ + public static final int FAILED_READ_METADATA = 567; + + /** Status code constant (value 568) indicating the platform could not write + * some of its metadata. + * Severity: error. Category: internal. + */ + public static final int FAILED_WRITE_METADATA = 568; + + /** Status code constant (value 569) indicating the platform could not delete + * some of its metadata. + * Severity: error. Category: internal. + */ + public static final int FAILED_DELETE_METADATA = 569; + + /** + * Returns the path of the resource associated with this status. + * + * @return the path of the resource related to this status + */ + public IPath getPath(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java new file mode 100644 index 0000000000..c1cdad2439 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * This interface is implemented by objects that visit resource trees. + *

      + * Usage: + *

      + * class Visitor implements IResourceVisitor {
      + *    public boolean visit(IResource res) {
      + *       // your code here
      + *       return true;
      + *    }
      + * }
      + * IResource root = ...;
      + * root.accept(new Visitor());
      + * 
      + *

      + *

      + * Clients may implement this interface. + *

      + * + * @see IResource#accept(IResourceVisitor) + */ +public interface IResourceVisitor { + /** + * Visits the given resource. + * + * @param resource the resource to visit + * @return true if the resource's members should + * be visited; false if they should be skipped + * @exception CoreException if the visit fails for some reason. + */ + public boolean visit(IResource resource) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java new file mode 100644 index 0000000000..3633fc7ab6 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java @@ -0,0 +1,197 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; + +/** + * A context for workspace save operations. + *

      + * Note that IWorkspace.save uses a + * different save context for each registered participant, + * allowing each to declare whether they have actively + * participated and decide whether to receive a resource + * delta on reactivation. + *

      + * + * @see IWorkspace#save(boolean, org.eclipse.core.runtime.IProgressMonitor) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ISaveContext { + + /*==================================================================== + * Constants related to save kind + *====================================================================*/ + + /** + * Type constant which identifies a full save. + * + * @see ISaveContext#getKind() + */ + public static final int FULL_SAVE = 1; + + /** + * Type constant which identifies a snapshot. + * + * @see ISaveContext#getKind() + */ + public static final int SNAPSHOT = 2; + + /** + * Type constant which identifies a project save. + * + * @see ISaveContext#getKind() + */ + public static final int PROJECT_SAVE = 3; + + /** + * Returns current files mapped with the ISaveContext.map + * facility or an empty array if there are no mapped files. + * + * @return the files currently mapped by the participant + * + * @see #map(IPath, IPath) + */ + public IPath[] getFiles(); + + /** + * Returns the type of this save. The types can be: + *
        + *
      • ISaveContext.FULL_SAVE
      • + *
      • ISaveContext.SNAPSHOT
      • + *
      • ISaveContext.PROJECT_SAVE
      • + *
      + * + * @return the type of the current save + */ + public int getKind(); + + /** + * Returns the number for the previous save in + * which the plug-in actively participated, or 0 + * if the plug-in has never actively participated in a save before. + *

      + * In the event of an unsuccessful save, this is the value to + * rollback to. + *

      + * + * @return the previous save number if positive, or 0 + * if never saved before + * @see ISaveParticipant#rollback(ISaveContext) + */ + public int getPreviousSaveNumber(); + + /** + * If the current save is a project save, this method returns the project + * being saved. + * + * @return the project being saved or null if this is not + * project save + * + * @see #getKind() + */ + public IProject getProject(); + + /** + * Returns the number for this save. This number is + * guaranteed to be 1 more than the + * previous save number. + *

      + * This is the value to use when, for example, creating files + * in which a participant will save its data. + *

      + * + * @return the save number + * @see ISaveParticipant#saving(ISaveContext) + */ + public int getSaveNumber(); + + /** + * Returns the current location for the given file or + * null if none. + * + * @return the location of a given file or null + * @see #map(IPath, IPath) + * @see ISavedState#lookup(IPath) + */ + public IPath lookup(IPath file); + + /** + * Maps the given plug-in file to its real location. This method is intended to be used + * with ISaveContext.getSaveNumber() to map plug-in configuration + * file names to real locations. + *

      + * For example, assume a plug-in has a configuration file named "config.properties". + * The map facility can be used to map that logical name onto a real + * name which is specific to a particular save (e.g., 10.config.properties, + * where 10 is the current save number). The paths specified here should + * always be relative to the plug-in state location for the plug-in saving the state. + *

      + *

      + * Each save participant must manage the deletion of its old state files. Old state files + * can be discovered using getPreviousSaveNumber or by using + * getFiles to discover the current files and comparing that to the + * list of files on disk. + *

      + * @param file the logical name of the participant's data file + * @param location the real (i.e., filesystem) name by which the file should be known + * for this save, or null to remove the entry + * @see #lookup(IPath) + * @see #getSaveNumber() + * @see #needSaveNumber() + * @see ISavedState#lookup(IPath) + */ + public void map(IPath file, IPath location); + + /** + * Indicates that the saved workspace tree should be remembered so that a delta + * will be available in a subsequent session when the plug-in re-registers + * to participate in saves. If this method is not called, no resource delta will + * be made available. This facility is not available for marker deltas. + * Plug-ins must assume that all markers may have changed when they are activated. + *

      + * Note that this is orthogonal to needSaveNumber. That is, + * one can ask for a delta regardless of whether or not one is an active participant. + *

      + *

      + * Note that deltas are not guaranteed to be saved even if saving is requested. + * Deltas cannot be supplied where the previous state is too old or has become invalid. + *

      + *

      + * This method is only valid for full saves. It is ignored during snapshots + * or project saves. + *

      + * + * @see IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant) + * @see ISavedState#processResourceChangeEvents(IResourceChangeListener) + */ + public void needDelta(); + + /** + * Indicates that this participant has actively participated in this save. + * If the save is successful, the current save number will be remembered; + * this save number will be the previous save number for subsequent saves + * until the participant again actively participates. + *

      + * If this method is not called, the plug-in is not deemed to be an active + * participant in this save. + *

      + *

      + * Note that this is orthogonal to needDelta. That is, + * one can be an active participant whether or not one asks for a delta. + *

      + * + * @see IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant) + * @see ISavedState#getSaveNumber() + */ + public void needSaveNumber(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java new file mode 100644 index 0000000000..eca59af269 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.util.EventListener; +import org.eclipse.core.runtime.CoreException; + +/** + * A participant in the saving of the workspace. + *

      + * Plug-ins implement this interface and register to participate + * in workspace save operations. + *

      + *

      + * Clients may implement this interface. + *

      + * @see IWorkspace#save(boolean, org.eclipse.core.runtime.IProgressMonitor) + */ +public interface ISaveParticipant extends EventListener { + /** + * Tells this participant that the workspace save operation is now + * complete and it is free to go about its normal business. + * Exceptions are not expected to be thrown at this point, so they + * should be handled internally. + *

      + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

      + * + * @param context the save context object + */ + public void doneSaving(ISaveContext context); + + /** + * Tells this participant that the workspace is about to be + * saved. In preparation, the participant is expected to suspend + * its normal operation until further notice. saving + * will be next, followed by either doneSaving + * or rollback depending on whether the workspace + * save was successful. + *

      + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

      + * + * @param context the save context object + * @exception CoreException if this method fails to snapshot + * the state of this workspace + */ + public void prepareToSave(ISaveContext context) throws CoreException; + + /** + * Tells this participant to rollback its important state. + * The context's previous state number indicates what it was prior + * to the failed save. + * Exceptions are not expected to be thrown at this point, so they + * should be handled internally. + *

      + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

      + * + * @param context the save context object + * @see ISaveContext#getPreviousSaveNumber() + */ + public void rollback(ISaveContext context); + + /** + * Tells this participant to save its important state because + * the workspace is being saved, as described in the supplied + * save context. + *

      + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

      + *

      + * The basic contract for this method is the same for full saves, + * snapshots and project saves: the participant must absolutely guarantee that any + * important user data it has gathered will not be irrecoverably lost + * in the event of a crash. The only difference is in the space-time + * tradeoffs that the participant should make. + *

        + *
      • Full saves: the participant is + * encouraged to save additional non-essential information that will aid + * it in retaining user state and configuration information and quickly getting + * back in sync with the state of the platform at a later point. + *
      • + *
      • Snapshots: the participant is discouraged from saving non-essential + * information that could be recomputed in the unlikely event of a crash. + * This lifecycle event will happen often and participant actions should take + * an absolute minimum of time. + *
      • + *
      • Project saves: the participant should only save project related data. + * It is discouraged from saving non-essential information that could be recomputed + * in the unlikely event of a crash. + *
      • + *
      + * For instance, the Java IDE gathers various user preferences and would want to + * make sure that the current settings are safe and sound after a + * save (if not saved immediately). + * The Java IDE would likely save computed image builder state on full saves, + * because this would allow the Java IDE to be restarted later and not + * have to recompile the world at that time. On the other hand, the Java + * IDE would not save the image builder state on a snapshot because + * that information is non-essential; in the unlikely event of a crash, + * the image should be rebuilt either from scratch or from the last saved + * state. + *

      + *

      + * The following snippet shows how a plug-in participant would write + * its important state to a file whose name is based on the save + * number for this save operation. + *

      +	 *     Plugin plugin = ...; // known
      +	 *     int saveNumber = context.getSaveNumber();
      +	 *     String saveFileName = "save-" + Integer.toString(saveNumber);
      +	 *     File f = plugin.getStateLocation().append(saveFileName).toFile();
      +	 *     plugin.writeImportantState(f);
      +	 *     context.map(new Path("save"), new Path(saveFileName));
      +	 *     context.needSaveNumber();
      +	 *     context.needDelta(); // optional
      +	 * 
      + * When the plug-in is reactivated in a subsequent workspace session, + * it needs to re-register to participate in workspace saves. When it + * does so, it is handed back key information about what state it had last + * saved. If it's interested, it can also ask for a resource delta + * describing all resource changes that have happened since then, if this + * information is still available. + * The following snippet shows what a participant plug-in would + * need to do if and when it is reactivated: + *
      +	 *     IWorkspace ws = ...; // known
      +	 *     Plugin plugin = ...; // known
      +	 *     ISaveParticipant saver = ...; // known
      +	 *     ISavedState ss = ws.addSaveParticipant(plugin, saver);
      +	 *     if (ss == null) {
      +	 *         // activate for very first time
      +	 *         plugin.buildState();
      +	 *     } else {
      +	 *         String saveFileName = ss.lookup(new Path("save"));
      +	 *         File f = plugin.getStateLocation().append(saveFileName).toFile();
      +	 *         plugin.readImportantState(f);
      +	 *         IResourceChangeListener listener = new IResourceChangeListener() {
      +	 *             public void resourceChanged(IResourceChangeEvent event) {
      +	 *                 IResourceDelta delta = event.getDelta();
      +	 *                 if (delta != null) {
      +	 *                     // fast reactivation using delta
      +	 *                     plugin.updateState(delta);
      +	 *                 } else {
      +	 *                     // slower reactivation without benefit of delta
      +	 *                     plugin.rebuildState();
      +	 *                 }
      +	 *         };
      +	 *         ss.processResourceChangeEvents(listener);
      +	 *     }
      +	 * 
      + *

      + * + * @param context the save context object + * @exception CoreException if this method fails + * @see ISaveContext#getSaveNumber() + */ + public void saving(ISaveContext context) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java new file mode 100644 index 0000000000..1524c3ca92 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; + +/** + * A data structure returned by {@link IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant)} + * containing a save number and an optional resource delta. + * + * @see IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ISavedState { + /** + * Returns the files mapped with the {@link ISaveContext#map(IPath, IPath)} + * facility. Returns an empty array if there are no mapped files. + * + * @return the files currently mapped by the participant + * + * @see #lookup(IPath) + * @see ISaveContext#map(IPath, IPath) + */ + public IPath[] getFiles(); + + /** + * Returns the save number for the save participant. + * This is the save number of the last successful save in which the plug-in + * actively participated, or 0 if the plug-in has + * never actively participated in a successful save. + * + * @return the save number + */ + public int getSaveNumber(); + + /** + * Returns the mapped location associated with the given path + * or null if none. + * + * @return the mapped location of a given path + * @see #getFiles() + * @see ISaveContext#map(IPath, IPath) + */ + public IPath lookup(IPath file); + + /** + * Used to receive notification of changes that might have happened + * while this plug-in was not active. The listener receives notifications of changes to + * the workspace resource tree since the time this state was saved. After this + * method is run, the delta is forgotten. Subsequent calls to this method + * will have no effect. + *

      + * No notification is received in the following cases: + *

        + *
      • if a saved state was never recorded ({@link ISaveContext#needDelta()} + * was not called)
      • + *
      • a saved state has since been forgotten (using {@link IWorkspace#forgetSavedTree(String)})
      • + *
      • a saved state has been deemed too old or has become invalid
      • + *
      + *

      + * All clients should have a contingency plan in place in case + * a changes are not available (the case should be very similar + * to the first time a plug-in is activated, and only has the + * current state of the workspace to work from). + *

      + *

      + * The supplied event is of type {@link IResourceChangeEvent#POST_BUILD} + * and contains the delta detailing changes since this plug-in last participated + * in a save. This event object (and the resource delta within it) is valid only + * for the duration of the invocation of this method. + *

      + * + * @param listener the listener + * @see ISaveContext#needDelta() + * @see IResourceChangeListener + */ + public void processResourceChangeEvents(IResourceChangeListener listener); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java new file mode 100644 index 0000000000..1f0a4e261c --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.io.InputStream; +import org.eclipse.core.runtime.*; + +/** + * A storage object represents a set of bytes which can be accessed. + * These may be in the form of an IFile or IFileState + * or any other object supplied by user code. The main role of an IStorage + * is to provide a uniform API for access to, and presentation of, its content. + *

      + * Storage objects implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

      + * Clients may implement this interface. + *

      + *

      + */ +public interface IStorage extends IAdaptable { + /** + * Returns an open input stream on the contents of this storage. + * The caller is responsible for closing the stream when finished. + * + * @return an input stream containing the contents of this storage + * @exception CoreException if the contents of this storage could + * not be accessed. See any refinements for more information. + */ + public InputStream getContents() throws CoreException; + + /** + * Returns the full path of this storage. The returned value + * depends on the implementor/extender. A storage need not + * have a path. + * + * @return the path related to the data represented by this storage or + * null if none. + */ + public IPath getFullPath(); + + /** + * Returns the name of this storage. + * The name of a storage is synonymous with the last segment + * of its full path though if the storage does not have a path, + * it may still have a name. + * + * @return the name of the data represented by this storage, + * or null if this storage has no name + * @see #getFullPath() + */ + public String getName(); + + /** + * Returns whether this storage is read-only. + * + * @return true if this storage is read-only + */ + public boolean isReadOnly(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java new file mode 100644 index 0000000000..4e2547a0c6 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.QualifiedName; + +/** + * A synchronizer which maintains a list of registered partners and, on behalf of + * each partner, it keeps resource level synchronization information + * (a byte array). Sync info is saved only when the workspace is saved. + * + * @see IWorkspace#getSynchronizer() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ISynchronizer { + /** + * Visits the given resource and its descendents with the specified visitor + * if sync information for the given sync partner is found on the resource. If + * sync information for the given sync partner is not found on the resource, + * still visit the children of the resource if the depth specifies to do so. + * + * @param partner the sync partner name + * @param start the parent resource to start the visitation + * @param visitor the visitor to use when visiting the resources + * @param depth the depth to which members of this resource should be + * visited. One of IResource.DEPTH_ZERO, IResource.DEPTH_ONE, + * or IResource.DEPTH_INFINITE. + * @exception CoreException if this operation fails. Reasons include: + *
        + *
      • The resource does not exist.
      • + *
      • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
      • + *
      + */ + public void accept(QualifiedName partner, IResource start, IResourceVisitor visitor, int depth) throws CoreException; + + /** + * Adds the named synchronization partner to this synchronizer's + * registry of partners. Once a partner's name has been registered, sync + * information can be set and retrieve on resources relative to the name. + * Adding a sync partner multiple times has no effect. + * + * @param partner the partner name to register + * @see #remove(QualifiedName) + */ + public void add(QualifiedName partner); + + /** + * Discards the named partner's synchronization information + * associated with the specified resource and its descendents to the + * specified depth. + * + * @param partner the sync partner name + * @param resource the resource + * @param depth the depth to which members of this resource should be + * visited. One of IResource.DEPTH_ZERO, IResource.DEPTH_ONE, + * or IResource.DEPTH_INFINITE. + * @exception CoreException if this operation fails. Reasons include: + *
        + *
      • The resource does not exist.
      • + *
      • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
      • + *
      + */ + public void flushSyncInfo(QualifiedName partner, IResource resource, int depth) throws CoreException; + + /** + * Returns a list of synchronization partner names currently registered + * with this synchronizer. Returns an empty array if there are no + * registered sync partners. + * + * @return a list of sync partner names + */ + public QualifiedName[] getPartners(); + + /** + * Returns the named sync partner's synchronization information for the given resource. + * Returns null if no information is found. + * + * @param partner the sync partner name + * @param resource the resource + * @return the synchronization information, or null if none + * @exception CoreException if this operation fails. Reasons include: + *
        + *
      • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
      • + *
      + */ + public byte[] getSyncInfo(QualifiedName partner, IResource resource) throws CoreException; + + /** + * Removes the named synchronization partner from this synchronizer's + * registry. Does nothing if the partner is not registered. + * This discards all sync information for the defunct partner. + * After a partner has been unregistered, sync information for it can no + * longer be stored on resources. + * + * @param partner the partner name to remove from the registry + * @see #add(QualifiedName) + */ + public void remove(QualifiedName partner); + + /** + * Sets the named sync partner's synchronization information for the given resource. + * If the given info is non-null and the resource neither exists + * nor is a phantom, this method creates a phantom resource to hang on to the info. + * If the given info is null, any sync info for the resource stored by the + * given sync partner is discarded; in some cases, this may result in the deletion + * of a phantom resource if there is no more sync info to maintain for that resource. + *

      + * Sync information is not stored on the workspace root. Attempts to set information + * on the root will be ignored. + *

      + * + * @param partner the sync partner name + * @param resource the resource + * @param info the synchronization information, or null + * @exception CoreException if this operation fails. Reasons include: + *
        + *
      • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
      • + *
      + */ + public void setSyncInfo(QualifiedName partner, IResource resource, byte[] info) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java new file mode 100644 index 0000000000..c39f3d99a6 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java @@ -0,0 +1,1748 @@ +/******************************************************************************* + * 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 + * Red Hat Incorporated - loadProjectDescription(InputStream) + * Broadcom Corporation - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.io.InputStream; +import java.net.URI; +import java.util.Map; +import org.eclipse.core.resources.team.FileModificationValidationContext; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * Workspaces are the basis for Eclipse Platform resource management. There is + * only one workspace per running platform. All resources exist in the context + * of this workspace. + *

      + * A workspace corresponds closely to discreet areas in the local file system. + * Each project in a workspace maps onto a specific area of the file system. The + * folders and files within a project map directly onto the corresponding + * directories and files in the file system. One sub-directory, the workspace + * metadata area, contains internal information about the workspace and its + * resources. This metadata area should be accessed only by the Platform or via + * Platform API calls. + *

      + *

      + * Workspaces add value over using the file system directly in that they allow + * for comprehensive change tracking (through IResourceDelta s), + * various forms of resource metadata (e.g., markers and properties) as well as + * support for managing application/tool state (e.g., saving and restoring). + *

      + *

      + * The workspace as a whole is thread safe and allows one writer concurrent with + * multiple readers. It also supports mechanisms for saving and snapshotting the + * current resource state. + *

      + *

      + * The workspace is provided by the Resources plug-in and is automatically + * created when that plug-in is activated. The default workspace data area + * (i.e., where its resources are stored) overlap exactly with the platform's + * data area. That is, by default, the workspace's projects are found directly + * in the platform's data area. Individual project locations can be specified + * explicitly. + *

      + *

      + * The workspace resource namespace is always case-sensitive and + * case-preserving. Thus the workspace allows multiple sibling resources to exist + * with names that differ only in case. The workspace also imposes no + * restrictions on valid characters in resource names, the length of resource names, + * or the size of resources on disk. In situations where one or more resources + * are stored in a file system that is not case-sensitive, or that imposes restrictions + * on resource names, any failure to store or retrieve those resources will + * be propagated back to the caller of workspace API. + *

      + *

      + * Workspaces implement the IAdaptable interface; extensions are + * managed by the platform's adapter manager. + *

      + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IWorkspace extends IAdaptable { + /** + * flag constant (bit mask value 1) indicating that resource change + * notifications should be avoided during the invocation of a compound + * resource changing operation. + * + * @see IWorkspace#run(IWorkspaceRunnable, ISchedulingRule, int, IProgressMonitor) + * @since 3.0 + */ + public static final int AVOID_UPDATE = 1; + + /** + * Constant that can be passed to {@link #validateEdit(org.eclipse.core.resources.IFile[], Object)} + * to indicate that the caller does not have access to a UI context but would still + * like to have UI-based validation if possible. + * @since 3.3 + * @see #validateEdit(IFile[], Object) + */ + public static final Object VALIDATE_PROMPT = FileModificationValidationContext.VALIDATE_PROMPT; + + /** + * The name of the IWorkspace OSGi service (value "org.eclipse.core.resources.IWorkspace"). + * @since 3.5 + */ + public static final String SERVICE_NAME = IWorkspace.class.getName(); + + /** + * Adds the given listener for resource change events to this workspace. Has + * no effect if an identical listener is already registered. + *

      + * This method is equivalent to: + * + *

      +	 * addResourceChangeListener(listener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE);
      +	 * 
      + * + *

      + * + * @param listener the listener + * @see IResourceChangeListener + * @see IResourceChangeEvent + * @see #addResourceChangeListener(IResourceChangeListener, int) + * @see #removeResourceChangeListener(IResourceChangeListener) + */ + public void addResourceChangeListener(IResourceChangeListener listener); + + /** + * Adds the given listener for the specified resource change events to this + * workspace. Has no effect if an identical listener is already registered + * for these events. After completion of this method, the given listener + * will be registered for exactly the specified events. If they were + * previously registered for other events, they will be de-registered. + *

      + * Once registered, a listener starts receiving notification of changes to + * resources in the workspace. The resource deltas in the resource change + * event are rooted at the workspace root. Most resource change + * notifications occur well after the fact; the exception is + * pre-notification of impending project closures and deletions. The + * listener continues to receive notifications until it is replaced or + * removed. + *

      + *

      + * Listeners can listen for several types of event as defined in + * IResourceChangeEvent. Clients are free to register for + * any number of event types however if they register for more than one, it + * is their responsibility to ensure they correctly handle the case where + * the same resource change shows up in multiple notifications. Clients are + * guaranteed to receive only the events for which they are registered. + *

      + * + * @param listener the listener + * @param eventMask the bit-wise OR of all event types of interest to the + * listener + * @see IResourceChangeListener + * @see IResourceChangeEvent + * @see #removeResourceChangeListener(IResourceChangeListener) + */ + public void addResourceChangeListener(IResourceChangeListener listener, int eventMask); + + /** + * Registers the given plug-in's workspace save participant, and returns an + * object describing the workspace state at the time of the last save in + * which the plug-in participated. + *

      + * Once registered, the workspace save participant will actively participate + * in the saving of this workspace. + *

      + * + * @param plugin the plug-in + * @param participant the participant + * @return the last saved state in which the plug-in participated, or + * null if the plug-in has not participated before + * @exception CoreException if the method fails to add the participant. + * Reasons include: + *
        + *
      • The previous state could not be recovered.
      • + *
      + * @see ISaveParticipant + * @see #removeSaveParticipant(Plugin) + * @deprecated Use {@link #addSaveParticipant(String, ISaveParticipant)} instead + */ + @Deprecated + public ISavedState addSaveParticipant(Plugin plugin, ISaveParticipant participant) throws CoreException; + + /** + * Registers the given plug-in's workspace save participant, and returns an + * object describing the workspace state at the time of the last save in + * which the bundle participated. + *

      + * Once registered, the workspace save participant will actively participate + * in the saving of this workspace. + *

      + * + * @param pluginId the unique identifier of the plug-in + * @param participant the participant + * @return the last saved state in which the plug-in participated, or + * null if the plug-in has not participated before + * @exception CoreException if the method fails to add the participant. + * Reasons include: + *
        + *
      • The previous state could not be recovered.
      • + *
      + * @see ISaveParticipant + * @see #removeSaveParticipant(String) + * @since 3.6 + */ + public ISavedState addSaveParticipant(String pluginId, ISaveParticipant participant) throws CoreException; + + /** + * Builds all projects in this workspace. Projects are built in the order + * specified in this workspace's description. Projects not mentioned in the + * order or for which the order cannot be determined are built in an + * undefined order after all other projects have been built. If no order is + * specified, the workspace computes an order determined by project + * references. + *

      + * This method may change resources; these changes will be reported in a + * subsequent resource change event. + *

      + *

      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

      + * + * @param kind the kind of build being requested. Valid values are + *
        + *
      • {@link IncrementalProjectBuilder#FULL_BUILD}- indicates a full build.
      • + *
      • {@link IncrementalProjectBuilder#INCREMENTAL_BUILD}- indicates a incremental build.
      • + *
      • {@link IncrementalProjectBuilder#CLEAN_BUILD}- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states.
      • + *
      + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic {@link IResourceStatus#BUILD_FAILED} + * code, but it could also be any other status code; it might + * also be a {@link MultiStatus}. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#build(IBuildConfiguration[], int, boolean, IProgressMonitor) + * @see IProject#build(int, IProgressMonitor) + * @see #computeProjectOrder(IProject[]) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @see IResourceRuleFactory#buildRule() + */ + public void build(int kind, IProgressMonitor monitor) throws CoreException; + + /** + * Build the build configurations specified in the passed in build configuration array. + *

      + * Build order is determined by the workspace description and the project build configuration + * reference graph. + *

      + *

      + * If buildReferences is true, build configurations reachable through the build configuration graph are + * built as part of this build invocation. + *

      + *

      + * This method may change resources; these changes will be reported in a + * subsequent resource change event. + *

      + *

      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

      + * @param buildConfigs array of configurations to build + * @param kind the kind of build being requested. Valid values are + *
        + *
      • {@link IncrementalProjectBuilder#FULL_BUILD}- indicates a full build.
      • + *
      • {@link IncrementalProjectBuilder#INCREMENTAL_BUILD}- indicates a incremental build.
      • + *
      • {@link IncrementalProjectBuilder#CLEAN_BUILD}- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states.
      • + *
      + * @param buildReferences boolean indicating if references should be transitively built. + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic {@link IResourceStatus#BUILD_FAILED} + * code, but it could also be any other status code; it might + * also be a {@link MultiStatus}. + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * + * @see IProject#build(int, IProgressMonitor) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @see IResourceRuleFactory#buildRule() + * @since 3.7 + */ + public void build(IBuildConfiguration[] buildConfigs, int kind, boolean buildReferences, IProgressMonitor monitor) throws CoreException; + + /** + * Checkpoints the operation currently in progress. This method is used in + * the middle of a group of operations to force a background autobuild (if + * the build argument is true) and send an interim notification of resource + * change events. + *

      + * When invoked in the dynamic scope of a call to the + * IWorkspace.run method, this method reports a single + * resource change event describing the net effect of all changes done to + * resources since the last round of notifications. When the outermost + * run method eventually completes, it will do another + * autobuild (if enabled) and report the resource changes made after this + * call. + *

      + *

      + * This method has no effect if invoked outside the dynamic scope of a call + * to the IWorkspace.run method. + *

      + *

      + * This method should be used under controlled circumstance (e.g., to break + * up extremely long-running operations). + *

      + * + * @param build whether or not to run a build + * @see IWorkspace#run(IWorkspaceRunnable, ISchedulingRule, int, IProgressMonitor) + */ + public void checkpoint(boolean build); + + /** + * Returns the prerequisite ordering of the given projects. The computation + * is done by interpreting the projects' active build configuration references + * as dependency relationships. + * For example if A references B and C, and C references B, this method, + * given the list A, B, C will return the order B, C, A. That is, projects + * with no dependencies are listed first. + *

      + * The return value is a two element array of project arrays. The first + * project array is the list of projects which could be sorted (as outlined + * above). The second element of the return value is an array of the + * projects which are ambiguously ordered (e.g., they are part of a cycle). + *

      + *

      + * Cycles and ambiguities are handled by elimination. Projects involved in + * cycles are simply cut out of the ordered list and returned in an + * undefined order. Closed and non-existent projects are ignored and do not + * appear in the returned value at all. + *

      + * + * @param projects the projects to order + * @return the projects in sorted order and a list of projects which could + * not be ordered + * @deprecated Replaced by IWorkspace.computeProjectOrder, + * which produces a more usable result when there are cycles in project + * reference graph. + */ + @Deprecated + public IProject[][] computePrerequisiteOrder(IProject[] projects); + + /** + * Data structure for holding the multi-part outcome of + * IWorkspace.computeProjectOrder. + *

      + * This class is not intended to be instantiated by clients. + *

      + * + * @see IWorkspace#computeProjectOrder(IProject[]) + * @since 2.1 + */ + public final class ProjectOrder { + /** + * Creates an instance with the given values. + *

      + * This class is not intended to be instantiated by clients. + *

      + * + * @param projects initial value of projects field + * @param hasCycles initial value of hasCycles field + * @param knots initial value of knots field + */ + public ProjectOrder(IProject[] projects, boolean hasCycles, IProject[][] knots) { + this.projects = projects; + this.hasCycles = hasCycles; + this.knots = knots; + } + + /** + * A list of projects ordered so as to honor the project reference, and + * build configuration reference, relationships between these projects + * wherever possible. + * The elements are a subset of the ones passed as the projects + * parameter to IWorkspace.computeProjectOrder, where + * inaccessible (closed or non-existent) projects have been omitted. + */ + public IProject[] projects; + /** + * Indicates whether any of the accessible projects in + * projects are involved in non-trivial cycles. + * true if the reference graph contains at least + * one cycle involving two or more of the projects in + * projects, and false if none of the + * projects in projects are involved in cycles. + */ + public boolean hasCycles; + /** + * A list of knots in the project reference graph. This list is empty if + * the project reference graph does not contain cycles. If the project + * reference graph contains cycles, each element is a knot of two or + * more accessible projects from projects that are + * involved in a cycle of mutually dependent references. + */ + public IProject[][] knots; + } + + /** + * Computes a total ordering of the given projects based on both static and + * dynamic project references. If an existing and open project P references + * another existing and open project Q also included in the list, then Q + * should come before P in the resulting ordering. Closed and non-existent + * projects are ignored, and will not appear in the result. References to + * non-existent or closed projects are also ignored, as are any + * self-references. The total ordering is always consistent with the global + * total ordering of all open projects in the workspace. + *

      + * When there are choices, the choice is made in a reasonably stable way. + * For example, given an arbitrary choice between two projects, the one with + * the lower collating project name is usually selected. + *

      + *

      + * When the project reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references + * P1, P4 references P3, and P2 and P3 reference each other, then exactly + * one of the relationships between P2 and P3 will have to be ignored. The + * outcome will be either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result + * also contains complete details of any cycles present. + *

      + *

      + * This method is time-consuming and should not be called unnecessarily. + * There are a very limited set of changes to a workspace that could affect + * the outcome: creating, renaming, or deleting a project; opening or + * closing a project; adding or removing a project reference. + *

      + * + * @param projects the projects to order + * @return result describing the project order + * @since 2.1 + */ + public ProjectOrder computeProjectOrder(IProject[] projects); + + /** + * Copies the given sibling resources so that they are located as members of + * the resource at the given path; the names of the copies are the same as + * the corresponding originals. + *

      + * This is a convenience method, fully equivalent to: + * + *

      +	 * copy(resources, destination, (force ? IResource.FORCE : IResource.NONE), monitor);
      +	 * 
      + * + *

      + *

      + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been added to the new parent. + *

      + *

      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

      + * + * @param resources the resources to copy + * @param destination the destination container path + * @param force a flag controlling whether resources that are not in sync + * with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status object with code OK if there were no + * problems; otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to copy some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #copy(IResource[],IPath,int,IProgressMonitor) + */ + public IStatus copy(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Copies the given sibling resources so that they are located as members of + * the resource at the given path; the names of the copies are the same as + * the corresponding originals. + *

      + * This method can be expressed as a series of calls to + * IResource.copy(IPath,int,IProgressMonitor), with "best + * effort" semantics: + *

        + *
      • Resources are copied in the order specified, using the given update + * flags.
      • + *
      • Duplicate resources are only copied once.
      • + *
      • The method fails if the resources are not all siblings.
      • + *
      • The failure of an individual copy does not necessarily prevent the + * method from attempting to copy other resources.
      • + *
      • The method fails if there are projects among the resources.
      • + *
      • The method fails if the path of the resources is a prefix of the + * destination path.
      • + *
      • This method also fails if one or more of the individual resource + * copy steps fails.
      • + *
      + *

      + *

      + * After successful completion, corresponding new resources will now exist + * as members of the resource at the given path. + *

      + *

      + * The supplied path may be absolute or relative. Absolute paths fully + * specify the new location for the resource, including its project. + * Relative paths are considered to be relative to the container of the + * resources being copied. A trailing separator is ignored. + *

      + *

      + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been added to the new parent. + *

      + *

      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

      + * + * @param resources the resources to copy + * @param destination the destination container path + * @param updateFlags bit-wise or of update flag constants + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status object with code OK if there were no + * problems; otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to copy some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. Reasons include: + *
        + *
      • One of the resources does not exist.
      • + *
      • The resources are not siblings.
      • + *
      • One of the resources, or one of its descendents, is not local.
      • + *
      • The resource corresponding to the destination path does not exist. + *
      • + *
      • The resource corresponding to the parent destination path is a + * closed project.
      • + *
      • A corresponding target resource does exist.
      • + *
      • A resource of a different type exists at the target path.
      • + *
      • One of the resources is a project.
      • + *
      • The path of one of the resources is a prefix of the destination + * path.
      • + *
      • One of the resources, or one of its descendents, is out of sync with + * the local file system and FORCE is not specified.
      • + *
      • Resource changes are disallowed during certain types of resource + * change event notification. See IResourceChangeEvent for + * more details.
      • + *
      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#copy(IPath,int,IProgressMonitor) + * @see IResourceRuleFactory#copyRule(IResource, IResource) + * @since 2.0 + */ + public IStatus copy(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes the given resources. + *

      + * This is a convenience method, fully equivalent to: + * + *

      +	 * delete(resources, IResource.KEEP_HISTORY | (force ? IResource.FORCE : IResource.NONE), monitor);
      +	 * 
      + * + *

      + *

      + * This method changes resources; these changes will be reported in a + * subsequent resource change event. + *

      + *

      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

      + * + * @param resources the resources to delete + * @param force a flag controlling whether resources that are not in sync + * with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to delete some resource. The + * status contained in the exception is a multi-status indicating where the + * individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #delete(IResource[],int,IProgressMonitor) + */ + public IStatus delete(IResource[] resources, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes the given resources. + *

      + * This method can be expressed as a series of calls to + * IResource.delete(int,IProgressMonitor). + *

      + *

      + * The semantics of multiple deletion are: + *

        + *
      • Resources are deleted in the order presented, using the given update + * flags.
      • + *
      • Resources that do not exist are ignored.
      • + *
      • An individual deletion fails if the resource still exists + * afterwards.
      • + *
      • The failure of an individual deletion does not prevent the method + * from attempting to delete other resources.
      • + *
      • This method fails if one or more of the individual resource + * deletions fails; that is, if at least one of the resources in the list + * still exists at the end of this method.
      • + *
      + *

      + *

      + * This method changes resources; these changes will be reported in a + * subsequent resource change event. + *

      + *

      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

      + * + * @param resources the resources to delete + * @param updateFlags bit-wise or of update flag constants + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to delete some resource. The + * status contained in the exception is a multi-status indicating where the + * individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + * @see IResourceRuleFactory#deleteRule(IResource) + * @since 2.0 + */ + public IStatus delete(IResource[] resources, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Removes the given markers from the resources with which they are + * associated. Markers that do not exist are ignored. + *

      + * This method changes resources; these changes will be reported in a + * subsequent resource change event. + *

      + * + * @param markers the markers to remove + * @exception CoreException if this method fails. Reasons include: + *
        + *
      • Resource changes are disallowed during certain types of resource + * change event notification. See IResourceChangeEvent for + * more details.
      • + *
      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void deleteMarkers(IMarker[] markers) throws CoreException; + + /** + * Forgets any resource tree being saved for the plug-in with the given + * name. If the plug-in id is null, all trees are forgotten. + *

      + * Clients should not call this method unless they have a reason to do so. A + * plug-in which uses ISaveContext.needDelta in the process + * of a save indicates that it would like to be fed the resource delta the + * next time it is reactivated. If a plug-in never gets reactivated (or if + * it fails to successfully register to participate in workspace saves), the + * workspace nevertheless retains the necessary information to generate the + * resource delta if asked. This method allows such a long term leak to be + * plugged. + *

      + * + * @param pluginId the unique identifier of the plug-in, or null + * @see ISaveContext#needDelta() + */ + public void forgetSavedTree(String pluginId); + + /** + * Returns all filter matcher descriptors known to this workspace. Returns an empty + * array if there are no installed filter matchers. + * + * @return the filter matcher descriptors known to this workspace + * @since 3.6 + */ + public IFilterMatcherDescriptor[] getFilterMatcherDescriptors(); + + /** + * Returns the filter descriptor with the given unique identifier, or + * null if there is no such filter. + * + * @param filterMatcherId the filter matcher extension identifier (e.g. + * "com.example.coolFilter"). + * @return the filter matcher descriptor, or null + * @since 3.6 + */ + public IFilterMatcherDescriptor getFilterMatcherDescriptor(String filterMatcherId); + + /** + * Returns all nature descriptors known to this workspace. Returns an empty + * array if there are no installed natures. + * + * @return the nature descriptors known to this workspace + * @since 2.0 + */ + public IProjectNatureDescriptor[] getNatureDescriptors(); + + /** + * Returns the nature descriptor with the given unique identifier, or + * null if there is no such nature. + * + * @param natureId the nature extension identifier (e.g. + * "com.example.coolNature"). + * @return the nature descriptor, or null + * @since 2.0 + */ + public IProjectNatureDescriptor getNatureDescriptor(String natureId); + + /** + * Finds all dangling project references in this workspace. Projects which + * are not open are ignored. Returns a map with one entry for each open + * project in the workspace that has at least one dangling project + * reference; the value of the entry is an array of projects which are + * referenced by that project but do not exist in the workspace. Returns an + * empty Map if there are no projects in the workspace. + * + * @return a map (key type: IProject, value type: + * IProject[]) from project to dangling project references + */ + public Map getDanglingReferences(); + + /** + * Returns the workspace description. This object is responsible for + * defining workspace preferences. The returned value is a modifiable copy + * but changes are not automatically applied to the workspace. In order to + * changes take effect, IWorkspace.setDescription needs to be + * called. The workspace description values are store in the preference + * store. + * + * @return the workspace description + * @see #setDescription(IWorkspaceDescription) + */ + public IWorkspaceDescription getDescription(); + + /** + * Returns the root resource of this workspace. + * + * @return the workspace root + */ + public IWorkspaceRoot getRoot(); + + /** + * Returns a factory for obtaining scheduling rules prior to modifying + * resources in the workspace. + * + * @see IResourceRuleFactory + * @return a resource rule factory + * @since 3.0 + */ + public IResourceRuleFactory getRuleFactory(); + + /** + * Returns the synchronizer for this workspace. + * + * @return the synchronizer + * @see ISynchronizer + */ + public ISynchronizer getSynchronizer(); + + /** + * Returns whether this workspace performs autobuilds. + * + * @return true if autobuilding is on, false + * otherwise + */ + public boolean isAutoBuilding(); + + /** + * Returns whether the workspace tree is currently locked. Resource changes + * are disallowed during certain types of resource change event + * notification. See IResourceChangeEvent for more details. + * + * @return boolean true if the workspace tree is locked, + * false otherwise + * @see IResourceChangeEvent + * @since 2.1 + */ + public boolean isTreeLocked(); + + /** + * Reads the project description file (".project") from the given location + * in the local file system. This object is useful for discovering the + * correct name for a project before importing it into the workspace. + *

      + * The returned value is writeable. + *

      + * + * @param projectDescriptionFile the path in the local file system of an + * existing project description file + * @return a new project description + * @exception CoreException if the operation failed. Reasons include: + *
        + *
      • The project description file does not exist.
      • + *
      • The file cannot be opened or read.
      • + *
      • The file cannot be parsed as a legal project description.
      • + * + * @see #newProjectDescription(String) + * @see IProject#getDescription() + * @since 2.0 + */ + public IProjectDescription loadProjectDescription(IPath projectDescriptionFile) throws CoreException; + + /** + * Reads the project description file (".project") from the given InputStream. + * This object will not attempt to set the location since the project may not + * have a valid location on the local file system. + * This object is useful for discovering the correct name for a project before + * importing it into the workspace. + *

        + * The returned value is writeable. + *

        + * + * @param projectDescriptionFile an InputStream pointing to an existing project + * description file + * @return a new project description + * @exception CoreException if the operation failed. Reasons include: + *
          + *
        • The stream could not be read.
        • + *
        • The stream does not contain a legal project description.
        • + * + * @see #newProjectDescription(String) + * @see IProject#getDescription() + * @see IWorkspace#loadProjectDescription(IPath) + * @since 3.1 + */ + public IProjectDescription loadProjectDescription(InputStream projectDescriptionFile) throws CoreException; + + /** + * Moves the given sibling resources so that they are located as members of + * the resource at the given path; the names of the new members are the + * same. + *

          + * This is a convenience method, fully equivalent to: + * + *

          +	 * move(resources, destination, IResource.KEEP_HISTORY | (force ? IResource.FORCE : IResource.NONE), monitor);
          +	 * 
          + * + *

          + *

          + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been removed from their parent and that corresponding + * resources have been added to the new parent. Additional information + * provided with resource delta shows that these additions and removals are + * pairwise related. + *

          + *

          + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

          + * + * @param resources the resources to move + * @param destination the destination container path + * @param force a flag controlling whether resources that are not in sync + * with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages. + * @exception CoreException if the method fails to move some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #move(IResource[],IPath,int,IProgressMonitor) + */ + public IStatus move(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Moves the given sibling resources so that they are located as members of + * the resource at the given path; the names of the new members are the + * same. + *

          + * This method can be expressed as a series of calls to + * IResource.move, with "best effort" semantics: + *

            + *
          • Resources are moved in the order specified.
          • + *
          • Duplicate resources are only moved once.
          • + *
          • The force flag has the same meaning as it does on the + * corresponding single-resource method.
          • + *
          • The method fails if the resources are not all siblings.
          • + *
          • The method fails the path of any of the resources is a prefix of the + * destination path.
          • + *
          • The failure of an individual move does not necessarily prevent the + * method from attempting to move other resources.
          • + *
          • This method also fails if one or more of the individual resource + * moves fails; that is, if at least one of the resources in the list still + * exists at the end of this method.
          • + *
          • History is kept for moved files. When projects are moved, no history + * is kept
          • + *
          + *

          + *

          + * After successful completion, the resources and descendents will no longer + * exist; but corresponding new resources will now exist as members of the + * resource at the given path. + *

          + *

          + * The supplied path may be absolute or relative. Absolute paths fully + * specify the new location for the resource, including its project. + * Relative paths are considered to be relative to the container of the + * resources being moved. A trailing separator is ignored. + *

          + *

          + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been removed from their parent and that corresponding + * resources have been added to the new parent. Additional information + * provided with resource delta shows that these additions and removals are + * pairwise related. + *

          + *

          + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

          + * + * @param resources the resources to move + * @param destination the destination container path + * @param updateFlags bit-wise or of update flag constants + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages. + * @exception CoreException if the method fails to move some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. Reasons include: + *
            + *
          • One of the resources does not exist.
          • + *
          • The resources are not siblings.
          • + *
          • One of the resources, or one of its descendents, is not local.
          • + *
          • The resource corresponding to the destination path does not exist. + *
          • + *
          • The resource corresponding to the parent destination path is a + * closed project.
          • + *
          • A corresponding target resource does exist.
          • + *
          • A resource of a different type exists at the target path.
          • + *
          • The path of one of the resources is a prefix of the destination + * path.
          • + *
          • One of the resources, or one of its descendents, is out of sync with + * the local file system and FORCE is false. + *
          • + *
          • Resource changes are disallowed during certain types of resource + * change event notification. See IResourceChangeEvent for + * more details.
          • + *
          + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(IPath,int,IProgressMonitor) + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @since 2.0 + */ + public IStatus move(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Returns a new build configuration for the project, with the given name. + * The name is a human readable unique name for the build configuration in the + * project. The project need not exist. + *

          + * This API can be used to create {@link IBuildConfiguration}s that will be used as references + * to {@link IBuildConfiguration}s in other projects. These references are set using + * {@link IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[])} + * and may have a null configuration name which will resolve to the referenced + * project's active configuration when the reference is used. + *

          + *

          + * Build configuration do not become part of a project + * description until set using {@link IProjectDescription#setBuildConfigs(String[])}. + *

          + * + * @param projectName the name of the project on which the configuration will exist + * @param configName the name of the new build configuration + * @return a build configuration + * @see IProjectDescription#setBuildConfigs(String[]) + * @see IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[]) + * @see IBuildConfiguration + * @since 3.7 + */ + public IBuildConfiguration newBuildConfig(String projectName, String configName); + + /** + * Creates and returns a new project description for a project with the + * given name. This object is useful when creating, moving or copying + * projects. + *

          + * The project description is initialized to: + *

            + *
          • the given project name
          • + *
          • no references to other projects
          • + *
          • an empty build spec
          • + *
          • an empty comment
          • + *
          + *

          + *

          + * The returned value is writeable. + *

          + * + * @param projectName the name of the project + * @return a new project description + * @see IProject#getDescription() + * @see IProject#create(IProjectDescription, IProgressMonitor) + * @see IResource#copy(IProjectDescription, int, IProgressMonitor) + * @see IProject#move(IProjectDescription, boolean, IProgressMonitor) + */ + public IProjectDescription newProjectDescription(String projectName); + + /** + * Removes the given resource change listener from this workspace. Has no + * effect if an identical listener is not registered. + * + * @param listener the listener + * @see IResourceChangeListener + * @see #addResourceChangeListener(IResourceChangeListener) + */ + public void removeResourceChangeListener(IResourceChangeListener listener); + + /** + * Removes the workspace save participant for the given plug-in from this + * workspace. If no such participant is registered, no action is taken. + *

          + * Once removed, the workspace save participant no longer actively + * participates in any future saves of this workspace. + *

          + * + * @param plugin the plug-in + * @see ISaveParticipant + * @see #addSaveParticipant(Plugin, ISaveParticipant) + * @deprecated Use {@link #removeSaveParticipant(String)} instead + */ + @Deprecated + public void removeSaveParticipant(Plugin plugin); + + /** + * Removes the workspace save participant for the given plug-in from this + * workspace. If no such participant is registered, no action is taken. + *

          + * Once removed, the workspace save participant no longer actively + * participates in any future saves of this workspace. + *

          + * + * @param pluginId the unique identifier of the plug-in + * @see ISaveParticipant + * @see #addSaveParticipant(String, ISaveParticipant) + * @since 3.6 + */ + public void removeSaveParticipant(String pluginId); + + /** + * Runs the given action as an atomic workspace operation. + *

          + * After running a method that modifies resources in the workspace, + * registered listeners receive after-the-fact notification of what just + * transpired, in the form of a resource change event. This method allows + * clients to call a number of methods that modify resources and only have + * resource change event notifications reported at the end of the entire + * batch. + *

          + *

          + * If this method is called outside the dynamic scope of another such call, + * this method runs the action and then reports a single resource change + * event describing the net effect of all changes done to resources by the + * action. + *

          + *

          + * If this method is called in the dynamic scope of another such call, this + * method simply runs the action. + *

          + *

          + * The supplied scheduling rule is used to determine whether this operation + * can be run simultaneously with workspace changes in other threads. If the + * scheduling rule conflicts with another workspace change that is currently + * running, the calling thread will be blocked until that change completes. + * If the action attempts to make changes to the workspace that were not + * specified in the scheduling rule, it will fail. If no scheduling rule is + * supplied, there are no scheduling restrictions for this operation. + * If a non-null scheduling rule is supplied, this operation + * must always support cancelation in the case where this operation becomes + * blocked by a long running background operation. + *

          + *

          + * The AVOID_UPDATE flag controls whether periodic resource change + * notifications should occur during the scope of this call. If this flag is + * specified, and no other threads modify the workspace concurrently, then + * all resource change notifications will be deferred until the end of this + * call. If this flag is not specified, the platform may decide to broadcast + * periodic resource change notifications during the scope of this call. + *

          + *

          + * Flags other than AVOID_UPDATE are ignored. + *

          + * + * @param action the action to perform + * @param rule the scheduling rule to use when running this operation, or + * null if there are no scheduling restrictions for this + * operation. + * @param flags bit-wise or of flag constants (only AVOID_UPDATE is relevant + * here) + * @param monitor a progress monitor, or null if progress + * reporting is not desired. + * @exception CoreException if the operation failed. + * @exception OperationCanceledException if the operation is canceled. If a + * non-null scheduling rule is supplied, cancelation can occur + * even if no progress monitor is provided. + * + * @see #AVOID_UPDATE + * @see IResourceRuleFactory + * @since 3.0 + */ + public void run(IWorkspaceRunnable action, ISchedulingRule rule, int flags, IProgressMonitor monitor) throws CoreException; + + /** + * Runs the given action as an atomic workspace operation. + *

          + * This is a convenience method, fully equivalent to: + * + *

          +	 * workspace.run(action, workspace.getRoot(), IWorkspace.AVOID_UPDATE, monitor);
          +	 * 
          + * + *

          + * + * @param action the action to perform + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the operation failed. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + */ + public void run(IWorkspaceRunnable action, IProgressMonitor monitor) throws CoreException; + + /** + * Saves this workspace's valuable state on disk. Consults with all + * registered plug-ins so that they can coordinate the saving of their + * persistent state as well. + *

          + * The full parameter indicates whether a full save or a + * snapshot is being requested. Snapshots save the workspace information + * that is considered hard to be recomputed in the unlikely event of a + * crash. It includes parts of the workspace tree, workspace and projects + * descriptions, markers and sync information. Full saves are heavy weight + * operations which save the complete workspace state. + *

          + *

          + * To ensure that all outstanding changes to the workspace have been + * reported to interested parties prior to saving, a full save cannot be + * used within the dynamic scope of an IWorkspace.run + * invocation. Snapshots can be called any time and are interpreted by the + * workspace as a hint that a snapshot is required. The workspace will + * perform the snapshot when possible. Even as a hint, snapshots should only + * be called when necessary as they impact system performance. Although + * saving does not change the workspace per se, its execution is serialized + * like methods that write the workspace. + *

          + *

          + * The workspace is comprised of several different kinds of data with + * varying degrees of importance. The most important data, the resources + * themselves and their persistent properties, are written to disk + * immediately; other data are kept in volatile memory and only written to + * disk periodically; and other data are maintained in memory and never + * written out. The following table summarizes what gets saved when: + *

            + *
          • creating or deleting resource - immediately
          • + *
          • setting contents of file - immediately
          • + *
          • changes to project description - immediately
          • + *
          • session properties - never
          • + *
          • changes to persistent properties - immediately
          • + *
          • markers -save
          • + *
          • synchronizer info -save
          • + *
          • shape of the workspace resource tree -save
          • + *
          • list of active plug-ins - never
          • + *
          + * Resource-based plug-in also have data with varying degrees of importance. + * Each plug-in gets to decide the policy for protecting its data, either + * immediately, never, or at save time. For the latter, the + * plug-in coordinates its actions with the workspace (see + * ISaveParticipant for details). + *

          + *

          + * If the platform is shutdown (or crashes) after saving the workspace, any + * information written to disk by the last successful workspace + * save will be restored the next time the workspace is + * reopened for the next session. Naturally, information that is written to + * disk immediately will be as of the last time it was changed. + *

          + *

          + * The workspace provides a general mechanism for keeping concerned parties + * apprised of any and all changes to resources in the workspace ( + * IResourceChangeListener). It is even possible for a + * plug-in to find out about changes to resources that happen between + * workspace sessions (see IWorkspace.addSaveParticipant). + *

          + *

          + * At certain points during this method, the entire workspace resource tree + * must be locked to prevent resources from being changed (read access to + * resources is permitted). + *

          + *

          + * Implementation note: The execution sequence is as follows. + *

            + *
          • A long-term lock on the workspace is taken out to prevent further + * changes to workspace until the save is done.
          • + *
          • The list of saveable resource tree snapshots is initially empty. + *
          • + *
          • A different ISaveContext object is created for each + * registered workspace save participant plug-in, reflecting the kind of + * save (ISaveContext.getKind), the previous save number in + * which this plug-in actively participated, and the new save number (= + * previous save number plus 1).
          • + *
          • Each registered workspace save participant is sent + * prepareToSave(context), passing in its own context + * object. + *
              + *
            • Plug-in suspends all activities until further notice.
            • + *
            + * If prepareToSave fails (throws an exception), the problem + * is logged and the participant is marked as unstable.
          • + *
          • In dependent-before-prerequisite order, each registered workspace + * save participant is sent saving(context), passing in its + * own context object. + *
              + *
            • Plug-in decides whether it wants to actively participate in this + * save. The plug-in only needs to actively participate if some of its + * important state has changed since the last time it actively participated. + * If it does decide to actively participate, it writes its important state + * to a brand new file in its plug-in state area under a generated file name + * based on context.getStateNumber() and calls + * context.needStateNumber() to indicate that it has actively + * participated. If upon reactivation the plug-in will want a resource delta + * covering all changes between now and then, the plug-in should invoke + * context.needDelta() to request this now; otherwise, a + * resource delta for the intervening period will not be available on + * reactivation.
            • + *
            + * If saving fails (throws an exception), the problem is + * logged and the participant is marked as unstable.
          • + *
          • The plug-in save table contains an entry for each plug-in that has + * registered to participate in workspace saves at some time in the past + * (the list of plug-ins increases monotonically). Each entry records the + * save number of the last successful save in which that plug-in actively + * participated, and, optionally, a saved resource tree (conceptually, this + * is a complete tree; in practice, it is compressed into a special delta + * tree representation). A copy of the plug-in save table is made. Entries + * are created or modified for each registered plug-in to record the + * appropriate save number (either the previous save number, or the previous + * save number plus 1, depending on whether the participant was active and + * asked for a new number).
          • + *
          • The workspace tree, the modified copy of the plug-in save table, all + * markers, etc. and all saveable resource tree snapshots are written to + * disk as one atomic operation .
          • + *
          • The long-term lock on the workspace is released.
          • + *
          • If the atomic save succeeded: + *
              + *
            • The modified copy of the plug-in save table becomes the new plug-in + * save table.
            • + *
            • In prerequisite-before-dependent order, each registered workspace + * save participant is sent doneSaving(context), passing in + * its own context object. + *
                + *
              • Plug-in may perform clean up by deleting obsolete state files in its + * plug-in state area.
              • + *
              • Plug-in resumes its normal activities.
              • + *
              + * If doneSaving fails (throws an exception), the problem is + * logged and the participant is marked as unstable. (The state number in + * the save table is not rolled back just because of this instability.) + *
            • + *
            • The workspace save operation returns.
            • + *
            + *
          • If it failed: + *
              + *
            • The workspace previous state is restored.
            • + *
            • In prerequisite-before-dependent order, each registered workspace + * save participant is sent rollback(context), passing in + * its own context object. + *
                + *
              • Plug-in may perform clean up by deleting newly-created but obsolete + * state file in its plug-in state area.
              • + *
              • Plug-in resumes its normal activities.
              • + *
              + * If rollback fails (throws an exception), the problem is + * logged and the participant is marked as unstable. (The state number in + * the save table is rolled back anyway.)
            • + *
            • The workspace save operation fails.
            • + *
            + *
          • + *
          + *

          + *

          + * After a full save, the platform can be shutdown. This will cause the + * Resources plug-in and all the other plug-ins to shutdown, without + * disturbing the saved workspace on disk. + *

          + *

          + * When the platform is later restarted, activating the Resources plug-in + * opens the saved workspace. This reads into memory the workspace's + * resource tree, plug-in save table, and saved resource tree snapshots + * (everything that was written to disk in the atomic operation above). + * Later, when a plug-in gets reactivated and registers to participate in + * workspace saves, it is handed back the info from its entry in the plug-in + * save table, if it has one. It gets back the number of the last save in + * which it actively participated and, possibly, a resource delta. + *

          + *

          + * The only source of long term garbage would come from a plug-in that never + * gets reactivated, or one that gets reactivated but fails to register for + * workspace saves. (There is no such problem with a plug-in that gets + * uninstalled; its easy enough to scrub its state areas and delete its + * entry in the plug-in save table.) + *

          + * + * @param full true if this is a full save, and + * false if this is only a snapshot for protecting against + * crashes + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status that may contain warnings, such as the failure of an + * individual participant + * @exception CoreException if this method fails to save the state of this + * workspace. Reasons include: + *
            + *
          • The operation cannot be batched with others.
          • + *
          + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #addSaveParticipant(Plugin, ISaveParticipant) + */ + public IStatus save(boolean full, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the workspace description. Its values are stored in the preference + * store. + * + * @param description the new workspace description. + * @see #getDescription() + * @exception CoreException if the method fails. Reasons include: + *
            + *
          • There was a problem setting the workspace description.
          • + *
          + */ + public void setDescription(IWorkspaceDescription description) throws CoreException; + + /** + * Returns a copy of the given set of natures sorted in prerequisite order. + * For each nature, it is guaranteed that all of its prerequisites will + * precede it in the resulting array. + * + *

          + * Natures that are missing from the install or are involved in a + * prerequisite cycle are sorted arbitrarily. Duplicate nature IDs are + * removed, so the returned array may be smaller than the original. + *

          + * + * @param natureIds a valid set of nature extension identifiers + * @return the set of nature Ids sorted in prerequisite order + * @see #validateNatureSet(String[]) + * @since 2.0 + */ + public String[] sortNatureSet(String[] natureIds); + + /** + * Advises that the caller intends to modify the contents of the given files + * in the near future and asks whether modifying all these files would be + * reasonable. The files must all exist. This method is used to give the VCM + * component an opportunity to check out (or otherwise prepare) the files if + * required. (It is provided in this component rather than in the UI so that + * "core" (i.e., head-less) clients can use it. Similarly, it is located + * outside the VCM component for the convenience of clients that must also + * operate in configurations without VCM.) + *

          + *

          + * A client (such as an editor) should perform a validateEdit + * on a file whenever it finds itself in the following position: (a) the + * file is marked read-only, and (b) the client believes it likely (not + * necessarily certain) that it will modify the file's contents at some + * point. A case in point is an editor that has a buffer opened on a file. + * When the user starts to dirty the buffer, the editor should check to see + * whether the file is read-only. If it is, it should call + * validateEdit, and can reasonably expect this call, when + * successful, to cause the file to become read-write. An editor should also + * be sensitive to a file becoming read-only again even after a successful + * validateEdit (e.g., due to the user checking in the file + * in a different view); the editor should again call + * validateEdit if the file is read-only before attempting to + * save the contents of the file. + *

          + *

          + * By passing a UI context, the caller indicates that the VCM component may + * contact the user to help decide how best to proceed. If no UI context is + * provided, the VCM component will make its decision without additional + * interaction with the user. If OK is returned, the caller can safely + * assume that all of the given files haven been prepared for modification + * and that there is good reason to believe that + * IFile.setContents (or appendContents) + * would be successful on any of them. If the result is not OK, modifying + * the given files might not succeed for the reason(s) indicated. + *

          + *

          + * If a shell is passed in as the context, the VCM component may bring up a + * dialogs to query the user or report difficulties; the shell should be + * used to parent any such dialogs; the caller may safely assume that the + * reasons for failure will have been made clear to the user. If + * {@link IWorkspace#VALIDATE_PROMPT} is passed + * as the context, this indicates that the caller does not have access to + * a UI context but would still like the user to be prompted if required. + * If null is passed, the user should not be contacted; any + * failures should be reported via the result; the caller may chose to + * present these to the user however they see fit. The ideal implementation + * of this method is transactional; no files would be affected unless the + * go-ahead could be given. (In practice, there may be no feasible way to + * ensure such changes get done atomically.) + *

          + *

          + * The method calls FileModificationValidator.validateEdit + * for the file modification validator (if provided by the VCM plug-in). + * When there is no file modification validator, this method returns a + * status with an IResourceStatus.READ_ONLY_LOCAL code if one + * of the files is read-only, and a status with an IStatus.OK + * code otherwise. + *

          + *

          + * This method may be called from any thread. If the UI context is used, it + * is the responsibility of the implementor of + * FileModificationValidator.validateEdit to interact with + * the UI context in an appropriate thread. + *

          + * + * @param files the files that are to be modified; these files must all + * exist in the workspace + * @param context either {@link IWorkspace#VALIDATE_PROMPT}, + * or the org.eclipse.swt.widgets.Shell that is + * to be used to parent any dialogs with the user, or null if + * there is no UI context (declared as an Object to avoid any + * direct references on the SWT component) + * @return a status object that is OK if things are fine, + * otherwise a status describing reasons why modifying the given files is not + * reasonable. A status with a severity of CANCEL is returned + * if the validation was canceled, indicating the edit should not proceed. + * @see IResourceRuleFactory#validateEditRule(IResource[]) + * @since 2.0 + */ + public IStatus validateEdit(IFile[] files, Object context); + + /** + * Validates that the given resource will not (or would not, if the resource + * doesn't exist in the workspace yet) be filtered out from the workspace by + * its parent resource filters. + *

          + * Note that if the resource or its parent doesn't exist yet in the workspace, + * it is possible that it will still be effectively filtered out once the resource + * and/or its parent is created, even though this method doesn't report it. + * + * But if this method reports the resource as filtered, even though it, or its + * parent, doesn't exist in the workspace yet, it means that the resource will + * be filtered out by its parent resource filters once it exists in the workspace. + *

          + *

          + * This method will return a status with severity IStatus.ERROR + * if the resource will be filtered out - removed - out of the workspace by + * its parent resource filters. + *

          + *

          + * Note: linked resources and virtual folders are never filtered out by their + * parent resource filters. + * + * @param resource the resource to validate the location for + * @return a status object with code IStatus.OK if the given + * resource is not filtered by its parent resource filters, otherwise a status + * object with severity IStatus.ERROR indicating that it will + * @see IStatus#OK + * @since 3.6 + */ + public IStatus validateFiltered(IResource resource); + + /** + * Validates the given path as the location of the given resource on disk. + * The path must be either an absolute file system path, or a relative path + * whose first segment is the name of a defined workspace path variable. In + * addition to the restrictions for paths in general (see IPath. + * isValidPath), + * a link location must also obey the following rules: + *

            + *
          • must not overlap with the platform's metadata directory
          • + *
          • must not be the same as or a parent of the root directory of the + * project the linked resource is contained in
          • + *
          + *

          + * This method also checks that the given resource can legally become a + * linked resource. This includes the following restrictions: + *

            + *
          • must have a project as its immediate parent
          • + *
          • project natures and the team hook may disallow linked resources on + * projects they are associated with
          • + *
          • the global workspace preference to disable linking, + * ResourcesPlugin.PREF_DISABLE_LINKING must not be set to + * "true"
          • . + *
          + *

          + *

          + * This method will return a status with severity IStatus.ERROR + * if the location does not obey the above rules. Also, this method will + * return a status with severity IStatus.WARNING if the + * location overlaps the location of any existing resource in the workspace. + *

          + *

          + * Note: this method does not consider whether files or directories exist in + * the file system at the specified path. + * + * @param resource the resource to validate the location for + * @param location the location of the linked resource contents on disk + * @return a status object with code IStatus.OK if the given + * location is valid as the linked resource location, otherwise a status + * object with severity IStatus.WARNING or + * IStatus.ERROR indicating what is wrong with the location + * @see IStatus#OK + * @see ResourcesPlugin#PREF_DISABLE_LINKING + * @since 2.1 + */ + public IStatus validateLinkLocation(IResource resource, IPath location); + + /** + * Validates the given {@link URI} as the location of the given resource on disk. + * The location must be either an absolute URI, or a relative URI + * whose first segment is the name of a defined workspace path variable. + * A link location must obey the following rules: + *

            + *
          • must not overlap with the platform's metadata directory
          • + *
          • must not be the same as or a parent of the root directory of the + * project the linked resource is contained in
          • + *
          + *

          + * This method also checks that the given resource can legally become a + * linked resource. This includes the following restrictions: + *

            + *
          • must have a project as its immediate parent
          • + *
          • project natures and the team hook may disallow linked resources on + * projects they are associated with
          • + *
          • the global workspace preference to disable linking, + * ResourcesPlugin.PREF_DISABLE_LINKING must not be set to + * "true"
          • . + *
          + *

          + *

          + * This method will return a status with severity IStatus.ERROR + * if the location does not obey the above rules. Also, this method will + * return a status with severity IStatus.WARNING if the + * location overlaps the location of any existing resource in the workspace. + *

          + *

          + * Note: this method does not consider whether files or directories exist in + * the file system at the specified location. + * + * @param resource the resource to validate the location for + * @param location the location of the linked resource contents in some file system + * @return a status object with code IStatus.OK if the given + * location is valid as the linked resource location, otherwise a status + * object with severity IStatus.WARNING or + * IStatus.ERROR indicating what is wrong with the location + * @see IStatus#OK + * @see ResourcesPlugin#PREF_DISABLE_LINKING + * @since 3.2 + */ + public IStatus validateLinkLocationURI(IResource resource, URI location); + + /** + * Validates the given string as the name of a resource valid for one of the + * given types. + *

          + * In addition to the basic restrictions on paths in general (see + * {@link IPath#isValidSegment(String)}), a resource name must also not + * contain any characters or substrings that are not valid on the file system + * on which workspace root is located. In addition, the names "." and ".." + * are reserved due to their special meaning in file system paths. + *

          + *

          + * This validation check is done automatically as a resource is created (but + * not when the resource handle is constructed); this means that any + * resource that exists can be safely assumed to have a valid name and path. + * Note that the name of the workspace root resource is inherently invalid. + *

          + * + * @param segment the name segment to be checked + * @param typeMask bitwise-or of the resource type constants ( + * FILE,FOLDER,PROJECT or + * ROOT) indicating expected resource type(s) + * @return a status object with code IStatus.OK if the given + * string is valid as a resource name, otherwise a status object indicating + * what is wrong with the string + * @see IResource#PROJECT + * @see IResource#FOLDER + * @see IResource#FILE + * @see IStatus#OK + */ + public IStatus validateName(String segment, int typeMask); + + /** + * Validates that each of the given natures exists, and that all nature + * constraints are satisfied within the given set. + *

          + * The following conditions apply to validation of a set of natures: + *

            + *
          • all natures in the set exist in the plug-in registry + *
          • all prerequisites of each nature are present in the set + *
          • there are no cycles in the prerequisite graph of the set + *
          • there are no two natures in the set that specify one-of-nature + * inclusion in the same group. + *
          • there are no two natures in the set with the same id + *
          + *

          + *

          + * An empty nature set is always valid. + *

          + * + * @param natureIds an array of nature extension identifiers + * @return a status object with code IStatus.OK if the given + * set of natures is valid, otherwise a status object indicating what is + * wrong with the set + * @since 2.0 + */ + public IStatus validateNatureSet(String[] natureIds); + + /** + * Validates the given string as a path for a resource of the given type(s). + *

          + * In addition to the restrictions for paths in general (see + * IPath.isValidPath), a resource path should also obey the + * following rules: + *

            + *
          • a resource path should be an absolute path with no device id + *
          • its segments should be valid names according to + * validateName + *
          • a path for the workspace root must be the canonical root path + *
          • a path for a project should have exactly 1 segment + *
          • a path for a file or folder should have more than 1 segment + *
          • the first segment should be a valid project name + *
          • the second through penultimate segments should be valid folder names + *
          • the last segment should be a valid name of the given type + *
          + *

          + *

          + * Note: this method does not consider whether a resource at the specified + * path exists. + *

          + *

          + * This validation check is done automatically as a resource is created (but + * not when the resource handle is constructed); this means that any + * resource that exists can be safely assumed to have a valid name and path. + *

          + * + * @param path the path string to be checked + * @param typeMask bitwise-or of the resource type constants ( + * FILE,FOLDER,PROJECT, or + * ROOT) indicating expected resource type(s) + * @return a status object with code IStatus.OK if the given + * path is valid as a resource path, otherwise a status object indicating + * what is wrong with the string + * @see IResource#PROJECT + * @see IResource#FOLDER + * @see IResource#FILE + * @see IStatus#OK + * @see IResourceStatus#getPath() + */ + public IStatus validatePath(String path, int typeMask); + + /** + * Validates the given path as the location of the given project on disk. + * The path must be either an absolute file system path, or a relative path + * whose first segment is the name of a defined workspace path variable. In + * addition to the restrictions for paths in general (see IPath. + * isValidPath), + * a location path should also obey the following rules: + *
            + *
          • must not be the same as another open or closed project
          • + *
          • must not occupy the default location for any project, whether existing or not
          • + *
          • must not be the same as or a parent of the platform's working directory
          • + *
          • must not be the same as or a child of the location of any existing + * linked resource in the given project
          • + *
          + *

          + *

          + * Note: this method does not consider whether files or directories exist in + * the file system at the specified path. + *

          + * + * @param project the project to validate the location for, can be null + * if non default project location is validated + * @param location the location of the project contents on disk, or null + * if the default project location is used + * @return a status object with code IStatus.OK if the given + * location is valid as the project content location, otherwise a status + * object indicating what is wrong with the location + * @see IProjectDescription#getLocationURI() + * @see IProjectDescription#setLocation(IPath) + * @see IStatus#OK + */ + public IStatus validateProjectLocation(IProject project, IPath location); + + /** + * Validates the given URI as the location of the given project. + * The location must be either an absolute URI, or a relative URI + * whose first segment is the name of a defined workspace path variable. + * A project location must obey the following rules: + *
            + *
          • must not be the same as another open or closed project
          • + *
          • must not occupy the default location for any project, whether existing or not
          • + *
          • must not be the same as or a parent of the platform's working directory
          • + *
          • must not be the same as or a child of the location of any existing + * linked resource in the given project
          • + *
          + *

          + *

          + * Note: this method does not consider whether files or directories exist in + * the file system at the specified path. + *

          + * + * @param project the project to validate the location for, can be null + * if non default project location is validated + * @param location the location of the project contents on disk, or null + * if the default project location is used + * @return a status object with code IStatus.OK if the given + * location is valid as the project content location, otherwise a status + * object indicating what is wrong with the location + * @see IProjectDescription#getLocationURI() + * @see IProjectDescription#setLocationURI(URI) + * @see IStatus#OK + * @since 3.2 + */ + public IStatus validateProjectLocationURI(IProject project, URI location); + + /** + * Returns the path variable manager for this workspace. + * + * @return the path variable manager + * @see IPathVariableManager + * @since 2.1 + */ + public IPathVariableManager getPathVariableManager(); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java new file mode 100644 index 0000000000..bde0221ee4 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +/** + * A workspace description represents the workspace preferences. It can be + * used to query the current preferences and set new ones. The workspace + * preference values are stored in the preference store and are also accessible + * via the preference mechanism. Constants for the preference keys are found + * on the ResourcesPlugin class. + * + * @see IWorkspace#getDescription() + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see IWorkspace#newProjectDescription(String) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IWorkspaceDescription { + /** + * Returns the order in which projects in the workspace should be built. + * The returned value is null if the workspace's default build + * order is being used. + * + * @return the names of projects in the order they will be built, + * or null if the default build order should be used + * @see #setBuildOrder(String[]) + * @see ResourcesPlugin#PREF_BUILD_ORDER + */ + public String[] getBuildOrder(); + + /** + * Returns the maximum length of time, in milliseconds, a file state should be + * kept in the local history. This setting is ignored by the workspace when + * isApplyFileStatePolicy() returns false. + * + * @return the maximum time a file state should be kept in the local history + * represented in milliseconds + * @see #setFileStateLongevity(long) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_FILE_STATE_LONGEVITY + */ + public long getFileStateLongevity(); + + /** + * Returns the maximum number of times that the workspace should rebuild when + * builders affect projects that have already been built. + * + * @return the maximum number of times that the workspace should rebuild when + * builders affect projects that have already been built. + * @see #setMaxBuildIterations(int) + * @see ResourcesPlugin#PREF_MAX_BUILD_ITERATIONS + * @since 2.1 + */ + public int getMaxBuildIterations(); + + /** + * Returns the maximum number of states per file that can be stored in the local history. + * This setting is ignored by the workspace when isApplyFileStatePolicy() + * returns false. + * + * @return the maximum number of states per file that can be stored in the local history + * @see #setMaxFileStates(int) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_MAX_FILE_STATES + */ + public int getMaxFileStates(); + + /** + * Returns the maximum permitted size of a file, in bytes, to be stored in the + * local history. This setting is ignored by the workspace when + * isApplyFileStatePolicy() returns false. + * + * @return the maximum permitted size of a file to be stored in the local history + * @see #setMaxFileStateSize(long) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_MAX_FILE_STATE_SIZE + */ + public long getMaxFileStateSize(); + + /** + * Returns whether file states are discarded according to the policy specified by + * setFileStateLongevity(long), setMaxFileStates(int) + * and setMaxFileStateSize(long) methods. + * + * @return true if file states are removed due to the policy, + * false otherwise + * @see #setApplyFileStatePolicy(boolean) + * @see #setFileStateLongevity(long) + * @see #setMaxFileStates(int) + * @see #setMaxFileStateSize(long) + * @see ResourcesPlugin#PREF_APPLY_FILE_STATE_POLICY + * @since 3.6 + */ + public boolean isApplyFileStatePolicy(); + + /** + * Returns the interval between automatic workspace snapshots. + * + * @return the amount of time in milliseconds between automatic workspace snapshots + * @see #setSnapshotInterval(long) + * @see ResourcesPlugin#PREF_SNAPSHOT_INTERVAL + * @since 2.0 + */ + public long getSnapshotInterval(); + + /** + * Returns whether this workspace performs autobuilds. + * + * @return true if autobuilding is on, otherwise + * false + * @see #setAutoBuilding(boolean) + * @see ResourcesPlugin#PREF_AUTO_BUILDING + */ + public boolean isAutoBuilding(); + + /** + * Records whether this workspace performs autobuilds. + *

          + * When autobuild is on, any changes made to a project and its + * resources automatically triggers an incremental build of the workspace. + *

          + *

          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

          + * + * @param value true to turn on autobuilding, + * and false to turn it off + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #isAutoBuilding() + * @see ResourcesPlugin#PREF_AUTO_BUILDING + */ + public void setAutoBuilding(boolean value); + + /** + * Sets the order in which projects in the workspace should be built. + * Projects not named in this list are built in a default order defined + * by the workspace. Set this value to null to use the + * default ordering for all projects. Projects not named in the list are + * built in unspecified order after all ordered projects. + *

          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

          + * + * @param value the names of projects in the order in which they are built, + * or null to use the workspace's default order for all projects + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getBuildOrder() + * @see ResourcesPlugin#PREF_BUILD_ORDER + */ + public void setBuildOrder(String[] value); + + /** + * Sets the maximum time, in milliseconds, a file state should be kept in the + * local history. This setting is ignored by the workspace when setApplyFileStatePolicy(boolean) + * is set to false. + *

          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

          + * + * @param time the maximum number of milliseconds a file state should be + * kept in the local history + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getFileStateLongevity() + * @see #setApplyFileStatePolicy(boolean) + * @see ResourcesPlugin#PREF_FILE_STATE_LONGEVITY + */ + public void setFileStateLongevity(long time); + + /** + * Sets the maximum number of times that the workspace should rebuild when + * builders affect projects that have already been built. + *

          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

          + * + * @param number the maximum number of times that the workspace should rebuild + * when builders affect projects that have already been built. + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getMaxBuildIterations() + * @see ResourcesPlugin#PREF_MAX_BUILD_ITERATIONS + * @since 2.1 + */ + public void setMaxBuildIterations(int number); + + /** + * Sets the maximum number of states per file that can be stored in the local history. + * If the maximum number is reached, older states are removed in favor of + * new ones. This setting is ignored by the workspace when setApplyFileStatePolicy(boolean) + * is set to false. + *

          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

          + * + * @param number the maximum number of states per file that can be stored in the local history + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getMaxFileStates() + * @see #setApplyFileStatePolicy(boolean) + * @see ResourcesPlugin#PREF_MAX_FILE_STATES + */ + public void setMaxFileStates(int number); + + /** + * Sets the maximum permitted size of a file, in bytes, to be stored in the + * local history. This setting is ignored by the workspace when setApplyFileStatePolicy(boolean) + * is set to false. + *

          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

          + * + * @param size the maximum permitted size of a file to be stored in the local history + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getMaxFileStateSize() + * @see #setApplyFileStatePolicy(boolean) + * @see ResourcesPlugin#PREF_MAX_FILE_STATE_SIZE + */ + public void setMaxFileStateSize(long size); + + /** + * Sets whether file states are discarded according to the policy specified by + * setFileStateLongevity(long), setMaxFileStates(int) + * and setMaxFileStateSize(long) methods. + *

          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

          + * + * @param apply true if file states are removed due to the policy, + * false otherwise + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #setFileStateLongevity(long) + * @see #setMaxFileStates(int) + * @see #setMaxFileStateSize(long) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_APPLY_FILE_STATE_POLICY + * @since 3.6 + */ + public void setApplyFileStatePolicy(boolean apply); + + /** + * Sets the interval between automatic workspace snapshots. The new interval + * will only take effect after the next snapshot. + *

          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

          + * + * @param delay the amount of time in milliseconds between automatic workspace snapshots + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getSnapshotInterval() + * @see ResourcesPlugin#PREF_SNAPSHOT_INTERVAL + * @since 2.0 + */ + public void setSnapshotInterval(long delay); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java new file mode 100644 index 0000000000..31eb40b584 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java @@ -0,0 +1,394 @@ +/******************************************************************************* + * 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; + +import java.net.URI; +import org.eclipse.core.runtime.*; + +/** + * A root resource represents the top of the resource hierarchy in a workspace. + * There is exactly one root in a workspace. The root resource has the following + * behavior: + *
            + *
          • It cannot be moved or copied
          • + *
          • It always exists.
          • + *
          • Deleting the root deletes all of the children under the root but leaves the root itself
          • + *
          • It is always local.
          • + *
          • It is never a phantom.
          • + *
          + *

          + * Workspace roots implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

          + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IWorkspaceRoot extends IContainer, IAdaptable { + + /** + * Deletes everything in the workspace except the workspace root resource + * itself. + *

          + * This is a convenience method, fully equivalent to: + *

          +	 *   delete(
          +	 *     (deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT )
          +	 *        | (force ? FORCE : IResource.NONE),
          +	 *     monitor);
          +	 * 
          + *

          + *

          + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

          + *

          + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

          + * + * @param deleteContent a flag controlling how whether content is + * aggressively deleted + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
            + *
          • A project could not be deleted.
          • + *
          • A project's contents could not be deleted.
          • + *
          • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
          • + *
          + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the handles to all the resources (workspace root, project, + * folder) in the workspace which are mapped to the given path in the local + * file system. Returns an empty array if there are none. + *

          + * If the path maps to the platform working location, the returned object + * will be a single element array consisting of an object of type + * ROOT. + *

          + *

          + * If the path maps to a project, the resulting array will contain a + * resource of type PROJECT, along with any linked folders that + * share the same location. Otherwise the resulting array will contain + * folders (type FOLDER). + *

          + *

          + * The path should be absolute; a relative path will be treated as absolute. + * The path segments need not be valid names; a trailing separator is + * ignored. The resulting resources may not currently exist. + *

          + *

          + * The result will omit team private members and hidden resources. The + * result will omit resources within team private members or hidden + * containers. + *

          + *

          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

          + * + * @param location + * a path in the local file system + * @return the corresponding containers in the workspace, or an empty array + * if none + * @since 2.1 + * @deprecated use {@link #findContainersForLocationURI(URI)} instead + */ + @Deprecated + public IContainer[] findContainersForLocation(IPath location); + + /** + * Returns the handles to all the resources (workspace root, project, + * folder) in the workspace which are mapped to the given URI. Returns an + * empty array if there are none. + *

          + * If the path maps to the platform working location, the returned object + * will be a single element array consisting of an object of type + * ROOT. + *

          + *

          + * If the path maps to a project, the resulting array will contain a + * resource of type PROJECT, along with any linked folders that + * share the same location. Otherwise the resulting array will contain + * folders (type FOLDER). + *

          + *

          + * The URI must be absolute; its segments need not be valid names; a + * trailing separator is ignored. The resulting resources may not currently + * exist. + *

          + *

          + * The result will omit team private members and hidden resources. The + * result will omit resources within team private member sor hidden + * containers. + *

          + *

          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

          + *

          + * This is a convenience method, fully equivalent to + * findContainersForLocationURI(location, IResource.NONE). + *

          + * + * @param location + * a URI path into some file system + * @return the corresponding containers in the workspace, or an empty array + * if none + * @since 3.2 + */ + public IContainer[] findContainersForLocationURI(URI location); + + /** + * Returns the handles to all the resources (workspace root, project, + * folder) in the workspace which are mapped to the given URI. Returns an + * empty array if there are none. + *

          + * If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the + * member flags, team private members will be included along with the + * others. If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not + * specified (recommended), the result will omit any team private member + * resources. + *

          + *

          + * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, + * hidden members will be included along with the others. If the + * {@link #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 + * a URI path into some file system + * @param memberFlags + * bit-wise or of member flag constants ( + * {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} and {@link #INCLUDE_HIDDEN}) + * indicating which members are of interest + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 3.5 + */ + public IContainer[] findContainersForLocationURI(URI location, int memberFlags); + + /** + * Returns the handles of all files that are mapped to the given path in the + * local file system. Returns an empty array if there are none. The path + * should be absolute; a relative path will be treated as absolute. The path + * segments need not be valid names. The resulting files may not currently + * exist. + *

          + * The result will omit any team private member and hidden resources. The + * result will omit resources within team private member or hidden + * containers. + *

          + *

          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

          + * + * @param location + * a path in the local file system + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 2.1 + * @deprecated use {@link #findFilesForLocationURI(URI)} instead + */ + @Deprecated + public IFile[] findFilesForLocation(IPath location); + + /** + * Returns the handles of all files that are mapped to the given URI. + * Returns an empty array if there are none. The URI must be absolute; its + * path segments need not be valid names. The resulting files may not + * currently exist. + *

          + * The result will omit any team private member and hidden resources. The + * result will omit resources within team private member or hidden + * containers. + *

          + *

          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

          + *

          + * This is a convenience method, fully equivalent to + * findFilesForLocationURI(location, IResource.NONE). + *

          + * + * @param location + * a URI path into some file system + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 3.2 + */ + public IFile[] findFilesForLocationURI(URI location); + + /** + * Returns the handles of all files that are mapped to the given URI. + * Returns an empty array if there are none. The URI must be absolute; its + * path segments need not be valid names. The resulting files may not + * currently exist. + *

          + * If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the + * member flags, team private members will be included along with the + * others. If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not + * specified (recommended), the result will omit any team private member + * resources. + *

          + *

          + * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, + * hidden members will be included along with the others. If the + * {@link #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 + * a URI path into some file system + * @param memberFlags + * bit-wise or of member flag constants ( + * {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} and {@link #INCLUDE_HIDDEN}) + * indicating which members are of interest + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 3.5 + */ + public IFile[] findFilesForLocationURI(URI location, int memberFlags); + + /** + * Returns a handle to the workspace root, project or folder + * which is mapped to the given path + * in the local file system, or null if none. + * If the path maps to the platform working location, the returned object + * will be of type ROOT. If the path maps to a + * project, the resulting object will be + * of type PROJECT; otherwise the resulting object + * will be a folder (type FOLDER). + * The path should be absolute; a relative path will be treated as + * absolute. The path segments need not be valid names; a trailing separator is ignored. + * The resulting resource may not currently exist. + *

          + * This method returns null when the given file system location is not equal to + * or under the location of any existing project in the workspace, or equal to the + * location of the platform working location. + *

          + *

          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

          + *

          + * Warning: This method ignores linked resources and their children. Since + * linked resources may overlap other resources, a unique mapping from a + * file system location to a single resource is not guaranteed. To find all + * resources for a given location, including linked resources, use the method + * findContainersForLocation. + *

          + * + * @param location a path in the local file system + * @return the corresponding project or folder in the workspace, + * or null if none + */ + public IContainer getContainerForLocation(IPath location); + + /** + * Returns a handle to the file which is mapped to the given path + * in the local file system, or null if none. + * The path should be absolute; a relative path will be treated as + * absolute. The path segments need not be valid names. + * The resulting file may not currently exist. + *

          + * This method returns null when the given file system location is not under + * the location of any existing project in the workspace. + *

          + *

          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

          + *

          + * Warning: This method ignores linked resources and their children. Since + * linked resources may overlap other resources, a unique mapping from a + * file system location to a single resource is not guaranteed. To find all + * resources for a given location, including linked resources, use the method + * findFilesForLocation. + *

          + * + * @param location a path in the local file system + * @return the corresponding file in the workspace, + * or null if none + */ + public IFile getFileForLocation(IPath location); + + /** + * Returns a handle to the project resource with the given name + * which is a child of this root. The given name must be a valid + * path segment as defined by {@link IPath#isValidSegment(String)}. + *

          + * Note: This method deals exclusively with resource handles, + * independent of whether the resources exist in the workspace. + * With the exception of validating that the name is a valid path segment, + * validation checking of the project name is not done + * when the project handle is constructed; rather, it is done + * automatically as the project is created. + *

          + * + * @param name the name of the project + * @return a project resource handle + * @see #getProjects() + */ + public IProject getProject(String name); + + /** + * Returns the collection of projects which exist under this root. + * The projects can be open or closed. + *

          + * This is a convenience method, fully equivalent to getProjects(IResource.NONE). + * Hidden projects are not included. + *

          + * @return an array of projects + * @see #getProject(String) + * @see IResource#isHidden() + */ + public IProject[] getProjects(); + + /** + * Returns the collection of projects which exist under this root. + * The projects can be open or closed. + *

          + * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, hidden + * projects will be included along with the others. If the {@link #INCLUDE_HIDDEN} flag + * is not specified (recommended), the result will omit any hidden projects. + *

          + * + * @param memberFlags bit-wise or of member flag constants indicating which + * projects are of interest (only {@link #INCLUDE_HIDDEN} is currently applicable) + * @return an array of projects + * @see #getProject(String) + * @see IResource#isHidden() + * @since 3.4 + */ + public IProject[] getProjects(int memberFlags); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java new file mode 100644 index 0000000000..072d279f41 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A runnable which executes as a batch operation within the workspace. + * The IWorkspaceRunnable interface should be implemented + * by any class whose instances are intended to be run by + * IWorkspace.run. + *

          + * Clients may implement this interface. + *

          + * @see IWorkspace#run(IWorkspaceRunnable, IProgressMonitor) + */ +public interface IWorkspaceRunnable { + /** + * Runs the operation reporting progress to and accepting + * cancellation requests from the given progress monitor. + *

          + * Implementors of this method should check the progress monitor + * for cancellation when it is safe and appropriate to do so. The cancellation + * request should be propagated to the caller by throwing + * OperationCanceledException. + *

          + * + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @exception CoreException if this operation fails. + */ + public void run(IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java new file mode 100644 index 0000000000..f80b6eba22 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java @@ -0,0 +1,472 @@ +/******************************************************************************* + * 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 + * Anton Leherbauer (Wind River) - [198591] Allow Builder to specify scheduling rule + * Anton Leherbauer (Wind River) - [305858] Allow Builder to return null rule + * James Blackburn (Broadcom) - [306822] Provide Context for Builder getRule() + * Broadcom Corporation - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.util.Map; +import org.eclipse.core.internal.events.InternalBuilder; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * The abstract base class for all incremental project builders. This class + * provides the infrastructure for defining a builder and fulfills the contract + * specified by the org.eclipse.core.resources.builders standard + * extension point. + *

          + * All builders must subclass this class according to the following guidelines: + *

            + *
          • must re-implement at least build
          • + *
          • may implement other methods
          • + *
          • must supply a public, no-argument constructor
          • + *
          + * On creation, the setInitializationData method is called with + * any parameter data specified in the declaring plug-in's manifest. + */ +public abstract class IncrementalProjectBuilder extends InternalBuilder implements IExecutableExtension { + /** + * Build kind constant (value 6) indicating a full build request. A full + * build discards all previously built state and builds all resources again. + * Resource deltas are not applicable for this kind of build. + *

          + * Note: If there is no previous delta, a request for {@link #INCREMENTAL_BUILD} + * or {@link #AUTO_BUILD} will result in the builder being called with {@link #FULL_BUILD} + * build kind. + *

          + * + * @see IProject#build(int, IProgressMonitor) + * @see IProject#build(int, String, Map, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + */ + public static final int FULL_BUILD = 6; + /** + * Build kind constant (value 9) indicating an automatic build request. When + * autobuild is turned on, these builds are triggered automatically whenever + * resources change. Apart from the method by which autobuilds are triggered, + * they otherwise operate like an incremental build. + * + * @see IWorkspaceDescription#setAutoBuilding(boolean) + * @see IWorkspace#isAutoBuilding() + */ + public static final int AUTO_BUILD = 9; + /** + * Build kind constant (value 10) indicating an incremental build request. + * Incremental builds use an {@link IResourceDelta} that describes what + * resources have changed since the last build. The builder calculates + * what resources are affected by the delta, and rebuilds the affected resources. + * + * @see IProject#build(int, IProgressMonitor) + * @see IProject#build(int, String, Map, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + */ + public static final int INCREMENTAL_BUILD = 10; + /** + * Build kind constant (value 15) indicating a clean build request. A clean + * build discards any additional state that has been computed as a result of + * previous builds, and returns the project to a clean slate. Resource + * deltas are not applicable for this kind of build. + * + * @see IProject#build(int, IProgressMonitor) + * @see IProject#build(int, String, Map, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + * @see #clean(IProgressMonitor) + * @since 3.0 + */ + public static final int CLEAN_BUILD = 15; + + /** + * Runs this builder in the specified manner. Subclasses should implement + * this method to do the processing they require. + *

          + * If the build kind is {@link #INCREMENTAL_BUILD} or + * {@link #AUTO_BUILD}, the getDelta method can be + * used during the invocation of this method to obtain information about + * what changes have occurred since the last invocation of this method. Any + * resource delta acquired is valid only for the duration of the invocation + * of this method. A {@link #FULL_BUILD} has no associated build delta. + *

          + *

          + * After completing a build, this builder may return a list of projects for + * which it requires a resource delta the next time it is run. This + * builder's project is implicitly included and need not be specified. The + * build mechanism will attempt to maintain and compute deltas relative to + * the identified projects when asked the next time this builder is run. + * Builders must re-specify the list of interesting projects every time they + * are run as this is not carried forward beyond the next build. Projects + * mentioned in return value but which do not exist will be ignored and no + * delta will be made available for them. + *

          + *

          + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. All builders should report their progress and + * honor cancel requests in a timely manner. Cancelation requests should be + * propagated to the caller by throwing + * OperationCanceledException. + *

          + *

          + * All builders should try to be robust in the face of trouble. In + * situations where failing the build by throwing CoreException + * is the only option, a builder has a choice of how best to communicate the + * problem back to the caller. One option is to use the + * {@link IResourceStatus#BUILD_FAILED} status code along with a suitable message; + * another is to use a {@link MultiStatus} containing finer-grained problem + * diagnoses. + *

          + * + * @param kind the kind of build being requested. Valid values are + *
            + *
          • {@link #FULL_BUILD} - indicates a full build.
          • + *
          • {@link #INCREMENTAL_BUILD}- indicates an incremental build.
          • + *
          • {@link #AUTO_BUILD} - indicates an automatically triggered + * incremental build (autobuilding on).
          • + *
          + * @param args a table of builder-specific arguments keyed by argument name + * (key type: String, value type: String); + * null is equivalent to an empty map + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @return the list of projects for which this builder would like deltas the + * next time it is run or null if none + * @exception CoreException if this build fails. + * @see IProject#build(int, String, Map, IProgressMonitor) + */ + @Override + protected abstract IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException; + + /** + * Clean is an opportunity for a builder to discard any additional state that has + * been computed as a result of previous builds. It is recommended that builders + * override this method to delete all derived resources created by previous builds, + * and to remove all markers of type {@link IMarker#PROBLEM} that + * were created by previous invocations of the builder. The platform will + * take care of discarding the builder's last built state (there is no need + * to call forgetLastBuiltState). + *

          + *

          + * This method is called as a result of invocations of + * IWorkspace.build or IProject.build where + * the build kind is {@link #CLEAN_BUILD}. + *

          + * This default implementation does nothing. Subclasses may override. + *

          + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. All builders should report their progress and + * honor cancel requests in a timely manner. Cancelation requests should be + * propagated to the caller by throwing + * OperationCanceledException. + *

          + * + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @exception CoreException if this build fails. + * @see IWorkspace#build(int, IProgressMonitor) + * @see #CLEAN_BUILD + * @since 3.0 + */ + @Override + protected void clean(IProgressMonitor monitor) throws CoreException { + //default implementation does nothing + //thwart compiler warning + } + + /** + * Requests that this builder forget any state it may be retaining regarding + * previously built states. Typically this means that the next time the + * builder runs, it will have to do a full build since it does not have any + * state upon which to base an incremental build. + * This supersedes a call to {@link #rememberLastBuiltState()}. + */ + @Override + public final void forgetLastBuiltState() { + super.forgetLastBuiltState(); + } + + /** + * Requests that this builder remember any build invocation specific state. + * This means that the next time the builder runs, it will receive a delta + * which includes changes reported in the current {@link #getDelta(IProject)}. + *

          + * This can be used to indicate that a builder didn't run, even though there + * are changes, and the builder wishes that the delta be preserved until its + * next invocation. + *

          + * This is superseded by a call to {@link #forgetLastBuiltState()}. + * @since 3.7 + */ + @Override + public final void rememberLastBuiltState() { + super.rememberLastBuiltState(); + } + + /** + * Returns the build command associated with this builder. The returned + * command may or may not be in the build specification for the project + * on which this builder operates. + *

          + * Any changes made to the returned command will only take effect if + * the modified command is installed on a project build spec. + *

          + * + * @see IProjectDescription#setBuildSpec(ICommand []) + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @since 3.1 + */ + @Override + public final ICommand getCommand() { + return super.getCommand(); + } + + /** + * Returns the resource delta recording the changes in the given project + * since the last time this builder was run. null is returned + * if no such delta is available. An empty delta is returned if no changes + * have occurred, or if deltas are not applicable for the current build kind. + * If null is returned, clients should assume + * that unspecified changes have occurred and take the appropriate action. + *

          + * The system reserves the right to trim old state in an effort to conserve + * space. As such, callers should be prepared to receive null + * even if they previously requested a delta for a particular project by + * returning that project from a build call. + *

          + *

          + * A non- null delta will only be supplied for the given + * project if either the result returned from the previous + * build included the project or the project is the one + * associated with this builder. + *

          + *

          + * If the given project was mentioned in the previous build + * and subsequently deleted, a non- null delta containing the + * deletion will be returned. If the given project was mentioned in the + * previous build and was subsequently created, the returned + * value will be null. + *

          + *

          + * A valid delta will be returned only when this method is called during a + * build. The delta returned will be valid only for the duration of the + * enclosing build execution. + *

          + *

          + * The delta does not include changes made while this builder is running. + * If {@link #getRule(int, Map)} is overridden to return a scheduling rule other than + * the workspace root, changes performed in other threads during the build + * will not appear in the resource delta. + *

          + * + * @return the resource delta for the project or null + */ + @Override + public final IResourceDelta getDelta(IProject project) { + return super.getDelta(project); + } + + /** + * Returns the project for which this builder is defined. + * + * @return the project + */ + @Override + public final IProject getProject() { + return super.getProject(); + } + + /** + * Returns the build configuration for which this build was invoked. + * @return the build configuration + * @since 3.7 + */ + @Override + public final IBuildConfiguration getBuildConfig() { + return super.getBuildConfig(); + } + + /** + * Returns whether the given project has already been built during this + * build iteration. + *

          + * When the entire workspace is being built, the projects are built in + * linear sequence. This method can be used to determine if another project + * precedes this builder's project in that build sequence. If only a single + * project is being built, then there is no build order and this method will + * always return false. + *

          + * + * @param project the project to check against in the current build order + * @return true if the given project has been built in this + * iteration, and false otherwise. + * @see #needRebuild() + * @since 2.1 + */ + @Override + public final boolean hasBeenBuilt(IProject project) { + return super.hasBeenBuilt(project); + } + + /** + * Returns whether an interrupt request has been made for this build. + * Background autobuild is interrupted when another thread tries to modify + * the workspace concurrently with the build thread. When this occurs, the + * build cycle is flagged as interrupted and the build will be terminated at + * the earliest opportunity. This method allows long running builders to + * respond to this interruption in a timely manner. Builders are not + * required to respond to interruption requests. + *

          + * + * @return true if the build cycle has been interrupted, and + * false otherwise. + * @since 3.0 + */ + @Override + public final boolean isInterrupted() { + return super.isInterrupted(); + } + + /** + * Indicates that this builder made changes that affect a build configuration that + * precedes this build configuration in the currently executing build order, and thus a + * rebuild will be necessary. + *

          + * This is an advanced feature that builders should use with caution. This + * can cause workspace builds to iterate until no more builders require + * rebuilds. + *

          + * + * @see #hasBeenBuilt(IProject) + * @since 2.1 + */ + @Override + public final void needRebuild() { + super.needRebuild(); + } + + /** + * Sets initialization data for this builder. + *

          + * This method is part of the {@link IExecutableExtension} interface. + *

          + *

          + * Subclasses are free to extend this method to pick up initialization + * parameters from the plug-in plug-in manifest (plugin.xml) + * file, but should be sure to invoke this method on their superclass. + *

          + * For example, the following method looks for a boolean-valued parameter + * named "trace": + * + *

          +	 * public void setInitializationData(IConfigurationElement cfig, String propertyName, Object data) throws CoreException {
          +	 * 	super.setInitializationData(cfig, propertyName, data);
          +	 * 	if (data instanceof Hashtable) {
          +	 * 		Hashtable args = (Hashtable) data;
          +	 * 		String traceValue = (String) args.get("trace");
          +	 * 		TRACING = (traceValue != null && traceValue.equals("true"));
          +	 * 	}
          +	 * }
          +	 * 
          + *

          + * @throws CoreException if fails. + */ + @Override + public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException { + //default implementation does nothing + //thwart compiler warning + } + + /** + * Informs this builder that it is being started by the build management + * infrastructure. By the time this method is run, the builder's project is + * available and setInitializationData has been called. The + * default implementation should be called by all overriding methods. + * + * @see #setInitializationData(IConfigurationElement, String, Object) + */ + @Override + protected void startupOnInitialize() { + // reserved for future use + } + + /** + * Returns the scheduling rule that is required for building + * the project build configuration for which this builder is defined. The default + * is the workspace root rule. + *

          + * The scheduling rule determines which resources in the workspace are + * protected from being modified by other threads while the builder is running. Up until + * Eclipse 3.5, the entire workspace was always locked during a build; + * since Eclipse 3.6, builders can allow resources outside their scheduling + * rule to be modified. + *

          + * Notes: + *

            + *
          • + * If the builder rule is non-null it must be "contained" in the workspace root rule. + * I.e. {@link ISchedulingRule#contains(ISchedulingRule)} must return + * true when invoked on the workspace root with the builder rule. + *
          • + *
          • + * The rule returned here may have no effect if the build is invoked within the + * scope of another operation that locks the entire workspace. + *
          • + *
          • + * If this method returns any rule other than the workspace root, + * resources outside of the rule scope can be modified concurrently with the build. + * The delta returned by {@link #getDelta(IProject)} for any project + * outside the scope of the builder's rule may not contain changes that occurred + * concurrently with the build. + *
          + *

          + *

          + * Subclasses may override this method. + *

          + * @noreference This method is not intended to be referenced by clients. + * + * @param kind the kind of build being requested. Valid values include: + *
            + *
          • {@link #FULL_BUILD} - indicates a full build.
          • + *
          • {@link #INCREMENTAL_BUILD} - indicates an incremental build.
          • + *
          • {@link #AUTO_BUILD} - indicates an automatically triggered + * incremental build (autobuilding on).
          • + *
          • {@link #CLEAN_BUILD} - indicates a clean request.
          • + *
          + * @param args a table of builder-specific arguments keyed by argument name + * (key type: String, value type: String); + * null is equivalent to an empty map. + * @return a scheduling rule which is contained in the workspace root rule + * or null to indicate that no protection against resource + * modification during the build is needed. + * + * @since 3.6 + */ + public ISchedulingRule getRule(int kind, Map args) { + return ResourcesPlugin.getWorkspace().getRoot(); + } + + /** + * Get the context for this invocation of the builder. This is only valid + * in the context of a call to + * {@link #build(int, Map, IProgressMonitor)} + * + *

          + * This can be used to discover which build configurations are being built before + * and after this build configuration. + *

          + * + * @return the context for the most recent invocation of the builder + * @since 3.7 + */ + @Override + public final IBuildContext getContext() { + return super.getContext(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java new file mode 100644 index 0000000000..b74a3aba9a --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * 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 Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.internal.preferences.AbstractScope; +import org.eclipse.core.internal.preferences.EclipsePreferences; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; + +/** + * Object representing the project scope in the Eclipse preferences + * hierarchy. Can be used as a context for searching for preference + * values (in the org.eclipse.core.runtime.IPreferencesService + * APIs) or for determining the correct preference node to set values in the store. + *

          + * Project preferences are stored on a per project basis in the + * project's content area as specified by IProject#getLocation. + *

          + * The path for preferences defined in the project scope hierarchy + * is as follows: /project/<projectName>/<qualifier> + *

          + *

          + * This class is not intended to be subclassed. This class may be instantiated. + *

          + * @see IProject#getLocation() + * @since 3.0 + */ +public final class ProjectScope extends AbstractScope { + + /** + * String constant (value of "project") used for the + * scope name for this preference scope. + */ + public static final String SCOPE = "project"; //$NON-NLS-1$ + + private IProject context; + + /** + * Create and return a new project scope for the given project. The given + * project must not be null. + * + * @param context the project + * @exception IllegalArgumentException if the project is null + */ + public ProjectScope(IProject context) { + super(); + if (context == null) + throw new IllegalArgumentException(); + this.context = context; + } + + /* + * @see org.eclipse.core.runtime.IScopeContext#getNode(java.lang.String) + */ + @Override + public IEclipsePreferences getNode(String qualifier) { + if (qualifier == null) + throw new IllegalArgumentException(); + return (IEclipsePreferences) Platform.getPreferencesService().getRootNode().node(SCOPE).node(context.getName()).node(qualifier); + } + + /* + * @see org.eclipse.core.runtime.preferences.IScopeContext#getLocation() + */ + @Override + public IPath getLocation() { + IProject project = ((IResource) context).getProject(); + IPath location = project.getLocation(); + return location == null ? null : location.append(EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME); + } + + /* + * @see org.eclipse.core.runtime.preferences.IScopeContext#getName() + */ + @Override + public String getName() { + return SCOPE; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (!(obj instanceof ProjectScope)) + return false; + ProjectScope other = (ProjectScope) obj; + return context.equals(other.context); + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return super.hashCode() + context.getFullPath().hashCode(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java new file mode 100644 index 0000000000..ea55566fa5 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java @@ -0,0 +1,234 @@ +/******************************************************************************* + * Copyright (c) 2004, 2014 Red Hat Incorporated 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/org/documents/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API + * Red Hat Incorporated - initial implementation + * Martin Oberhuber (Wind River) - [44107] Add symbolic links to ResourceAttributes API + *******************************************************************************/ + +package org.eclipse.core.resources; + +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileSystem; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.runtime.CoreException; + +/** + * This class represents platform specific attributes of files. + * Any attributes can be added, but only the attributes that are + * supported by the platform will be used. These methods do not set the + * attributes in the file system. + * + * @author Red Hat Incorporated + * @see IResource#getResourceAttributes() + * @see IResource#setResourceAttributes(ResourceAttributes) + * @since 3.1 + * @noextend This class is not intended to be subclassed by clients. + */ +public class ResourceAttributes { + private int attributes; + + /** + * Creates a new resource attributes instance with attributes + * taken from the specified file in the file system. If the specified + * file does not exist or is not accessible, this method has the + * same effect as calling the default constructor. + * + * @param file The file to get attributes from + * @return A resource attributes object + */ + public static ResourceAttributes fromFile(java.io.File file) { + try { + return FileUtil.fileInfoToAttributes(EFS.getStore(file.toURI()).fetchInfo()); + } catch (CoreException e) { + //file could not be accessed + return new ResourceAttributes(); + } + } + + /** + * Creates a new instance of ResourceAttributes. + */ + public ResourceAttributes() { + super(); + } + + /** + * Returns whether this ResourceAttributes object is marked archive. + *

          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_ARCHIVE}.

          + * + * @return true if this resource is marked archive, + * false otherwise + * @see #setArchive(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_ARCHIVE + */ + public boolean isArchive() { + return (attributes & EFS.ATTRIBUTE_ARCHIVE) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked executable. + *

          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_EXECUTABLE}.

          + * + * @return true if this resource is marked executable, + * false otherwise + * @see #setExecutable(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_EXECUTABLE + */ + public boolean isExecutable() { + return (attributes & EFS.ATTRIBUTE_EXECUTABLE) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked hidden. + *

          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_HIDDEN}.

          + * + * @return true if this resource is marked hidden, + * false otherwise + * @see #setHidden(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_HIDDEN + * @since 3.2 + */ + public boolean isHidden() { + return (attributes & EFS.ATTRIBUTE_HIDDEN) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked read only. + *

          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_READ_ONLY}.

          + * + * @return true if this resource is marked as read only, + * false otherwise + * @see #setReadOnly(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_READ_ONLY + */ + public boolean isReadOnly() { + return (attributes & EFS.ATTRIBUTE_READ_ONLY) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked as symbolic link. + *

          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_SYMLINK}.

          + * + * @return true if this resource is marked as symbolic link, + * false otherwise + * @see #setSymbolicLink(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_SYMLINK + * @since 3.4 + */ + public boolean isSymbolicLink() { + return (attributes & EFS.ATTRIBUTE_SYMLINK) != 0; + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked archive. + *

          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_ARCHIVE}.

          + * + * @param archive true to set it to be archive, + * false to unset + * @see #isArchive() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_ARCHIVE + */ + public void setArchive(boolean archive) { + set(EFS.ATTRIBUTE_ARCHIVE, archive); + } + + /** + * Clears all of the bits indicated by the mask. + * @nooverride This method is not intended to be re-implemented or extended by clients. + * @noreference This method is not intended to be referenced by clients. + */ + public void set(int mask, boolean value) { + if (value) + attributes |= mask; + else + attributes &= ~mask; + } + + /** + * Returns whether this ResourceAttributes object has the given mask set. + * @nooverride This method is not intended to be re-implemented or extended by clients. + * @noreference This method is not intended to be referenced by clients. + */ + public boolean isSet(int mask) { + return (attributes & mask) != 0; + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked executable. + *

          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_EXECUTABLE}.

          + * + * @param executable true to set it to be executable, + * false to unset + * @see #isExecutable() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_EXECUTABLE + */ + public void setExecutable(boolean executable) { + set(EFS.ATTRIBUTE_EXECUTABLE, executable); + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked hidden + *

          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_HIDDEN}.

          + * + * @param hidden true to set it to be marked hidden, + * false to unset + * @see #isHidden() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_HIDDEN + * @since 3.2 + */ + public void setHidden(boolean hidden) { + set(EFS.ATTRIBUTE_HIDDEN, hidden); + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked read only. + *

          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_READ_ONLY}.

          + * + * @param readOnly true to set it to be marked read only, + * false to unset + * @see #isReadOnly() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_READ_ONLY + */ + public void setReadOnly(boolean readOnly) { + set(EFS.ATTRIBUTE_READ_ONLY, readOnly); + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked as symbolic link. + *

          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_SYMLINK}.

          + * + * @param symLink true to set it to be marked as symbolic link, + * false to unset + * @see #isSymbolicLink() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_SYMLINK + * @since 3.4 + */ + public void setSymbolicLink(boolean symLink) { + set(EFS.ATTRIBUTE_SYMLINK, symLink); + } + + /** + * Returns a string representation of the attributes, suitable + * for debugging purposes only. + */ + @Override + public String toString() { + return "ResourceAttributes(" + attributes + ')'; //$NON-NLS-1$ + } +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java new file mode 100644 index 0000000000..b539310f50 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java @@ -0,0 +1,484 @@ +/******************************************************************************* + * 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 + * Serge Beauchamp (Freescale Semiconductor) - [252996] add PT_FILTER_PROVIDERS + * Serge Beauchamp (Freescale Semiconductor) - [229633] add PT_VARIABLE_PROVIDERS + * 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.resources; + +import java.util.*; +import org.eclipse.core.internal.preferences.PreferencesService; +import org.eclipse.core.internal.resources.*; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.IJobManager; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.service.debug.DebugOptions; +import org.eclipse.osgi.service.debug.DebugOptionsListener; +import org.osgi.framework.*; + +/** + * The plug-in runtime class for the Resources plug-in. This is + * the starting point for all workspace and resource manipulation. + * A typical sequence of events would be for a dependent plug-in + * to call ResourcesPlugin.getWorkspace(). + * Doing so would cause this plug-in to be activated and the workspace + * (if any) to be loaded from disk and initialized. + * + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class ResourcesPlugin extends Plugin { + /** + * Unique identifier constant (value "org.eclipse.core.resources") + * for the standard Resources plug-in. + */ + public static final String PI_RESOURCES = "org.eclipse.core.resources"; //$NON-NLS-1$ + + /*==================================================================== + * Constants defining the ids of the standard workspace extension points: + *====================================================================*/ + + /** + * Simple identifier constant (value "builders") + * for the builders extension point. + */ + public static final String PT_BUILDERS = "builders"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "natures") + * for the natures extension point. + */ + public static final String PT_NATURES = "natures"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "markers") + * for the markers extension point. + */ + public static final String PT_MARKERS = "markers"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "fileModificationValidator") + * for the file modification validator extension point. + */ + public static final String PT_FILE_MODIFICATION_VALIDATOR = "fileModificationValidator"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "moveDeleteHook") + * for the move/delete hook extension point. + * + * @since 2.0 + */ + public static final String PT_MOVE_DELETE_HOOK = "moveDeleteHook"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "teamHook") + * for the team hook extension point. + * + * @since 2.1 + */ + public static final String PT_TEAM_HOOK = "teamHook"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "refreshProviders") + * for the auto-refresh refresh providers extension point. + * + * @since 3.0 + */ + public static final String PT_REFRESH_PROVIDERS = "refreshProviders"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "modelProviders") + * for the model providers extension point. + * + * @since 3.2 + */ + public static final String PT_MODEL_PROVIDERS = "modelProviders"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "variableProviders") + * for the variable providers extension point. + * + * @since 3.6 + */ + public static final String PT_VARIABLE_PROVIDERS = "variableResolvers"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "filterMatchers") + * for the filter matchers extension point. + * + * @since 3.6 + */ + public static final String PT_FILTER_MATCHERS = "filterMatchers"; //$NON-NLS-1$ + + /** + * Constant identifying the job family identifier for the background autobuild job. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @since 3.0 + */ + public static final Object FAMILY_AUTO_BUILD = new Object(); + + /** + * Constant identifying the job family identifier for the background auto-refresh job. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @since 3.1 + */ + public static final Object FAMILY_AUTO_REFRESH = new Object(); + + /** + * Constant identifying the job family identifier for a background build job. All clients + * that schedule background jobs for performing builds should include this job + * family in their implementation of belongsTo. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @see Job#belongsTo(Object) + * @since 3.0 + */ + public static final Object FAMILY_MANUAL_BUILD = new Object(); + + /** + * Constant identifying the job family identifier for a background refresh job. All clients + * that schedule background jobs for performing refreshing should include this job + * family in their implementation of belongsTo. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @see Job#belongsTo(Object) + * @since 3.4 + */ + public static final Object FAMILY_MANUAL_REFRESH = new Object(); + + /** + * Name of a preference indicating the encoding to use when reading text + * files in the workspace. The value is a string, and may + * be the default empty string, indicating that the file system encoding should + * be used instead. The file system encoding can be retrieved using + * System.getProperty("file.encoding"). + * There is also a convenience method getEncoding which returns + * the value of this preference, or the file system encoding if this + * preference is not set. + *

          + * Note that there is no guarantee that the value is a supported encoding. + * Callers should be prepared to handle UnsupportedEncodingException + * where this encoding is used. + *

          + * + * @see #getEncoding() + * @see java.io.UnsupportedEncodingException + */ + public static final String PREF_ENCODING = "encoding"; //$NON-NLS-1$ + + /** + * Common prefix for workspace preference names. + * @since 2.1 + */ + private static final String PREF_DESCRIPTION_PREFIX = "description."; //$NON-NLS-1$ + + /** + * @deprecated Do not use. + * @since 3.0 + */ + @Deprecated + public static final String PREF_MAX_NOTIFICATION_DELAY = "maxnotifydelay"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether the workspace performs auto- + * builds. + * + * @see IWorkspaceDescription#isAutoBuilding() + * @see IWorkspaceDescription#setAutoBuilding(boolean) + * @since 2.1 + */ + public static final String PREF_AUTO_BUILDING = PREF_DESCRIPTION_PREFIX + "autobuilding"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the order projects in the workspace + * are built. + * + * @see IWorkspaceDescription#getBuildOrder() + * @see IWorkspaceDescription#setBuildOrder(String[]) + * @since 2.1 + */ + public static final String PREF_BUILD_ORDER = PREF_DESCRIPTION_PREFIX + "buildorder"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether to use the workspace's + * default order for building projects. + * @since 2.1 + */ + public static final String PREF_DEFAULT_BUILD_ORDER = PREF_DESCRIPTION_PREFIX + "defaultbuildorder"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum number of times that the + * workspace should rebuild when builders affect projects that have already + * been built. + * + * @see IWorkspaceDescription#getMaxBuildIterations() + * @see IWorkspaceDescription#setMaxBuildIterations(int) + * @since 2.1 + */ + public static final String PREF_MAX_BUILD_ITERATIONS = PREF_DESCRIPTION_PREFIX + "maxbuilditerations"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether to apply the specified history size policy. + * + * @see IWorkspaceDescription#isApplyFileStatePolicy() + * @see IWorkspaceDescription#setApplyFileStatePolicy(boolean) + * @since 3.6 + */ + public static final String PREF_APPLY_FILE_STATE_POLICY = PREF_DESCRIPTION_PREFIX + "applyfilestatepolicy"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum number of milliseconds a + * file state should be kept in the local history + * + * @see IWorkspaceDescription#getFileStateLongevity() + * @see IWorkspaceDescription#setFileStateLongevity(long) + * @since 2.1 + */ + public static final String PREF_FILE_STATE_LONGEVITY = PREF_DESCRIPTION_PREFIX + "filestatelongevity"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum permitted size of a file + * to be stored in the local history + * + * @see IWorkspaceDescription#getMaxFileStateSize() + * @see IWorkspaceDescription#setMaxFileStateSize(long) + * @since 2.1 + */ + public static final String PREF_MAX_FILE_STATE_SIZE = PREF_DESCRIPTION_PREFIX + "maxfilestatesize"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum number of states per + * file that can be stored in the local history. + * + * @see IWorkspaceDescription#getMaxFileStates() + * @see IWorkspaceDescription#setMaxFileStates(int) + * @since 2.1 + */ + public static final String PREF_MAX_FILE_STATES = PREF_DESCRIPTION_PREFIX + "maxfilestates"; //$NON-NLS-1$ + /** + * Name of a preference for configuring the amount of time in milliseconds + * between automatic workspace snapshots + * + * @see IWorkspaceDescription#getSnapshotInterval() + * @see IWorkspaceDescription#setSnapshotInterval(long) + * @since 2.1 + */ + public static final String PREF_SNAPSHOT_INTERVAL = PREF_DESCRIPTION_PREFIX + "snapshotinterval"; //$NON-NLS-1$ + + /** + * Name of a preference for turning off support for linked resources. When + * this preference is set to "true", attempting to create linked resources will fail. + * @since 2.1 + */ + public static final String PREF_DISABLE_LINKING = PREF_DESCRIPTION_PREFIX + "disableLinking";//$NON-NLS-1$ + + /** + * Name of a preference for configuring whether the workspace performs auto- + * refresh. Auto-refresh installs a file-system listener, or performs + * periodic file-system polling to actively discover changes in the resource + * hierarchy. + * @since 3.0 + */ + public static final String PREF_AUTO_REFRESH = "refresh.enabled"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether out-of-sync resources are automatically + * asynchronously refreshed, when discovered to be out-of-sync by the workspace. + *

          + * This preference suppresses out-of-sync CoreException for some read methods, including: + * {@link IFile#getContents()} & {@link IFile#getContentDescription()}. + *

          + *

          + * In the future the workspace may enable other lightweight auto-refresh mechanisms when this + * preference is true. (The existing {@link ResourcesPlugin#PREF_AUTO_REFRESH} will continue + * to enable filesystem hooks and the existing polling based monitor.) + *

          + * See the discussion: https://bugs.eclipse.org/303517 + * @since 3.7 + */ + public static final String PREF_LIGHTWEIGHT_AUTO_REFRESH = "refresh.lightweight.enabled"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether encodings for derived + * resources within the project should be stored in a separate derived + * preference file. + * + * @since 3.7 + */ + public static final String PREF_SEPARATE_DERIVED_ENCODINGS = "separateDerivedEncodings"; //$NON-NLS-1$ + + /** + * Default setting for {@value #PREF_SEPARATE_DERIVED_ENCODINGS}. + * + * @since 3.9 + */ + public static final boolean DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS = false; + + /** + * The single instance of this plug-in runtime class. + */ + private static ResourcesPlugin plugin; + + /** + * The workspace managed by the single instance of this + * plug-in runtime class, or null is there is none. + */ + private static Workspace workspace = null; + + private ServiceRegistration workspaceRegistration; + private ServiceRegistration debugRegistration; + + /** + * Constructs an instance of this plug-in runtime class. + *

          + * An instance of this plug-in runtime class is automatically created + * when the facilities provided by the Resources plug-in are required. + * Clients must never explicitly instantiate a plug-in runtime class. + *

          + */ + public ResourcesPlugin() { + plugin = this; + } + + /** + * Constructs a brand new workspace structure at the location in the local file system + * identified by the given path and returns a new workspace object. + * + * @exception CoreException if the workspace structure could not be constructed. + * Reasons include: + *
            + *
          • There is an existing workspace structure on at the given location + * in the local file system. + *
          • A file exists at the given location in the local file system. + *
          • A directory could not be created at the given location in the + * local file system. + *
          + */ + private static void constructWorkspace() throws CoreException { + new LocalMetaArea().createMetaArea(); + } + + /** + * Returns the encoding to use when reading text files in the workspace. + * This is the value of the PREF_ENCODING preference, or the + * file system encoding (System.getProperty("file.encoding")) + * if the preference is not set. + *

          + * Note that this method does not check whether the result is a supported + * encoding. Callers should be prepared to handle + * UnsupportedEncodingException where this encoding is used. + * + * @return the encoding to use when reading text files in the workspace + * @see java.io.UnsupportedEncodingException + */ + public static String getEncoding() { + String enc = getPlugin().getPluginPreferences().getString(PREF_ENCODING); + if (enc == null || enc.length() == 0) { + enc = System.getProperty("file.encoding"); //$NON-NLS-1$ + } + return enc; + } + + /** + * Returns the Resources plug-in. + * + * @return the single instance of this plug-in runtime class + */ + public static ResourcesPlugin getPlugin() { + return plugin; + } + + /** + * Returns the workspace. The workspace is not accessible after the resources + * plug-in has shutdown. + * + * @return the workspace that was created by the single instance of this + * plug-in class. + */ + public static IWorkspace getWorkspace() { + if (workspace == null) + throw new IllegalStateException(Messages.resources_workspaceClosed); + return workspace; + } + + /** + * This implementation of the corresponding {@link BundleActivator} method + * closes the workspace without saving. + * @see BundleActivator#stop(BundleContext) + */ + @Override + public void stop(BundleContext context) throws Exception { + super.stop(context); + + // unregister debug options listener + debugRegistration.unregister(); + debugRegistration = null; + + if (workspace == null) + return; + workspaceRegistration.unregister(); + // save the preferences for this plug-in + getPlugin().savePluginPreferences(); + workspace.close(null); + + // Forget workspace only if successfully closed, to + // make it easier to debug cases where close() is failing. + workspace = null; + workspaceRegistration = null; + } + + /** + * This implementation of the corresponding {@link BundleActivator} method + * opens the workspace. + * @see BundleActivator#start(BundleContext) + */ + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + System.err.println("RESOURCES: BUG 519776"); + + // register debug options listener + Hashtable properties = new Hashtable(2); + properties.put(DebugOptions.LISTENER_SYMBOLICNAME, PI_RESOURCES); + debugRegistration = context.registerService(DebugOptionsListener.class, Policy.RESOURCES_DEBUG_OPTIONS_LISTENER, properties); + + if (!new LocalMetaArea().hasSavedWorkspace()) { + constructWorkspace(); + } + // Remember workspace before opening, to + // make it easier to debug cases where open() is failing. + workspace = new Workspace(); + PlatformURLResourceConnection.startup(workspace.getRoot().getLocation()); + initializePreferenceLookupOrder(); + IStatus result = workspace.open(null); + if (!result.isOK()) + getLog().log(result); + workspaceRegistration = context.registerService(IWorkspace.class, workspace, null); + } + + /* + * Add the project scope to the preference service's default look-up order so + * people get it for free + */ + private void initializePreferenceLookupOrder() { + PreferencesService service = PreferencesService.getDefault(); + String[] original = service.getDefaultDefaultLookupOrder(); + List newOrder = new ArrayList(); + // put the project scope first on the list + newOrder.add(ProjectScope.SCOPE); + for (String entry : original) + newOrder.add(entry); + service.setDefaultDefaultLookupOrder(newOrder.toArray(new String[newOrder.size()])); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java new file mode 100644 index 0000000000..4a739f7c86 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.internal.resources.InternalWorkspaceJob; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * A job that makes an atomic modification to the workspace. Clients must + * implement the abstract method runInWorkspace instead + * of the usual Job.run method. + *

          + * After running a method that modifies resources in the workspace, + * registered listeners receive after-the-fact notification of + * what just transpired, in the form of a resource change event. + * This method allows clients to call a number of + * methods that modify resources and only have resource + * change event notifications reported at the end of the entire + * batch. This mechanism is used to avoid unnecessary builds + * and notifications. + *

          + *

          + * Platform may decide to perform notifications during the operation. + * The reason for this is that it is possible for multiple threads + * to be modifying the workspace concurrently. When one thread finishes modifying + * the workspace, a notification is required to prevent responsiveness problems, + * even if the other operation has not yet completed. + *

          + *

          + * A WorkspaceJob is the asynchronous equivalent of IWorkspaceRunnable + *

          + *

          + * Note that the workspace is not locked against other threads during the execution + * of a workspace job. Other threads can be modifying the workspace concurrently + * with a workspace job. To obtain exclusive access to a portion of the workspace, + * set the scheduling rule on the job to be a resource scheduling rule. The + * interface IResourceRuleFactory is used to create a scheduling rule + * for a particular workspace modification operation. + *

          + * @see IWorkspaceRunnable + * @see org.eclipse.core.resources.IResourceRuleFactory + * @see IWorkspace#run(IWorkspaceRunnable, ISchedulingRule, int, IProgressMonitor) + * @since 3.0 + */ +public abstract class WorkspaceJob extends InternalWorkspaceJob { + /** + * Creates a new workspace 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 WorkspaceJob(String name) { + super(name); + } + + /** + * Runs the operation, reporting progress to and accepting + * cancelation requests from the given progress monitor. + *

          + * Implementors of this method should check the progress monitor + * for cancelation when it is safe and appropriate to do so. The cancelation + * request should be propagated to the caller by throwing + * OperationCanceledException. + *

          + * + * @param monitor a progress monitor, or null if progress + * reporting and cancelation are not desired + * @return the result of running the operation + * @exception CoreException if this operation fails. + */ + @Override + public abstract IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java new file mode 100644 index 0000000000..334e797eed --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 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 + *******************************************************************************/ +package org.eclipse.core.resources.filtermatchers; + +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; + +/** + * The abstract base class for all file info matchers. Instances + * of this class are provided using the org.eclipse.core.resources.filterMatchers + * extension point. + * + * @since 3.6 + */ +public abstract class AbstractFileInfoMatcher { + + /** + * Tests the given {@link FileInfo} + * + * @param parent the parent container + * @param fileInfo the {@link FileInfo} object to test + * @return true if the given {@link FileInfo} matches, + * and false otherwise. + * @throws CoreException the implementor should throw a CoreException if, + * in the case that the parent or fileInfo doesn't exist in the workspace + * or in the file system, the return value can't be determined. + */ + public abstract boolean matches(IContainer parent, IFileInfo fileInfo) throws CoreException; + + /** + * Sets initialization data for this matcher. + * @param project + * @param arguments + * @throws CoreException if initialization failed + */ + public abstract void initialize(IProject project, Object arguments) throws CoreException; +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java new file mode 100644 index 0000000000..4328540f10 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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) - [252996] initial API and implementation + * IBM Corporation - ongoing implementation + *******************************************************************************/ +package org.eclipse.core.resources.filtermatchers; + +import org.eclipse.core.internal.resources.FilterDescriptor; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; + +/** + * Resource Filter Type allowing serializing sub filters as the arguments + * @since 3.6 + */ +public abstract class CompoundFileInfoMatcher extends AbstractFileInfoMatcher { + + protected AbstractFileInfoMatcher[] matchers; + + private AbstractFileInfoMatcher instantiate(IProject project, FileInfoMatcherDescription filter) throws CoreException { + IFilterMatcherDescriptor desc = project.getWorkspace().getFilterMatcherDescriptor(filter.getId()); + if (desc != null) { + AbstractFileInfoMatcher matcher = ((FilterDescriptor) desc).createFilter(); + matcher.initialize(project, filter.getArguments()); + return matcher; + } + return null; + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.core.resources.AbstractFileInfoMatcher#initialize(org.eclipse + * .core.resources.IProject, java.lang.Object) + */ + @Override + public final void initialize(IProject project, Object arguments) throws CoreException { + FileInfoMatcherDescription[] filters = (FileInfoMatcherDescription[]) arguments; + matchers = new AbstractFileInfoMatcher[filters != null ? filters.length : 0]; + for (int i = 0; i < matchers.length; i++) + matchers[i] = instantiate(project, filters[i]); + } +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html new file mode 100644 index 0000000000..7f43f8e202 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html @@ -0,0 +1,21 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the resource filter matchers. + +

          Package Specification

          +

          +This package specifies the APIs in the Resources plug-in that are intended +to be implemented and used by plug-ins using the filterMatchers extension point. +

          +@since 3.6 +

          + + + diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java new file mode 100644 index 0000000000..2c5c7ccd09 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.*; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.*; + +/** + * A resource mapping that obtains the traversals for its model object + * from a set of child mappings. + *

          + * This class is not intended to be subclasses by clients. + * + * @since 3.2 + */ +public final class CompositeResourceMapping extends ResourceMapping { + + private final ResourceMapping[] mappings; + private final Object modelObject; + private IProject[] projects; + private String providerId; + + /** + * Create a composite mapping that obtains its traversals from a set of sub-mappings. + * @param modelObject the model object for this mapping + * @param mappings the sub-mappings from which the traversals are obtained + */ + public CompositeResourceMapping(String providerId, Object modelObject, ResourceMapping[] mappings) { + this.modelObject = modelObject; + this.mappings = mappings; + this.providerId = providerId; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.ResourceMapping#contains(org.eclipse.core.resources.mapping.ResourceMapping) + */ + @Override + public boolean contains(ResourceMapping mapping) { + for (int i = 0; i < mappings.length; i++) { + ResourceMapping childMapping = mappings[i]; + if (childMapping.contains(mapping)) { + return true; + } + } + return false; + } + + /** + * Return the resource mappings contained in this composite. + * @return Return the resource mappings contained in this composite. + */ + public ResourceMapping[] getMappings() { + return mappings; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.ResourceMapping#getModelObject() + */ + @Override + public Object getModelObject() { + return modelObject; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.ResourceMapping#getModelProviderId() + */ + @Override + public String getModelProviderId() { + return providerId; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.ResourceMapping#getProjects() + */ + @Override + public IProject[] getProjects() { + if (projects == null) { + Set result = new HashSet(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + result.addAll(Arrays.asList(mapping.getProjects())); + } + projects = result.toArray(new IProject[result.size()]); + } + return projects; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.mapping.ResourceMapping#getTraversals(org.eclipse.core.internal.resources.mapping.ResourceMappingContext, org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + if (monitor == null) + monitor = new NullProgressMonitor(); + try { + monitor.beginTask("", 100 * mappings.length); //$NON-NLS-1$ + List result = new ArrayList(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + result.addAll(Arrays.asList(mapping.getTraversals(context, new SubProgressMonitor(monitor, 100)))); + } + return result.toArray(new ResourceTraversal[result.size()]); + } finally { + monitor.done(); + } + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java new file mode 100644 index 0000000000..e49ded3932 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 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.mapping; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A model provider descriptor contains information about a model provider + * obtained from the plug-in manifest (plugin.xml) file. + *

          + * Model provider descriptors are platform-defined objects that exist + * independent of whether that model provider's plug-in has been started. + * In contrast, a model provider's runtime object (ModelProvider) + * generally runs plug-in-defined code. + *

          + * + * @see org.eclipse.core.resources.mapping.ModelProvider + * @since 3.2 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IModelProviderDescriptor { + + /** + * Return the ids of model providers that this model provider extends. + * @return the ids of model providers that this model provider extends + */ + public String[] getExtendedModels(); + + /** + * Returns the unique identifier of this model provider. + *

          + * The model provider identifier is composed of the model provider's + * plug-in id and the simple id of the provider extension. For example, if + * plug-in "com.xyz" defines a provider extension with id + * "myModelProvider", the unique model provider identifier will be + * "com.xyz.myModelProvider". + *

          + * + * @return the unique model provider identifier + */ + public String getId(); + + /** + * Returns a displayable label for this model provider. + * Returns the empty string if no label for this provider + * is specified in the plug-in manifest file. + *

          Note that any translation specified in the plug-in manifest + * file is automatically applied. + *

          + * + * @return a displayable string label for this model provider, + * possibly the empty string + */ + public String getLabel(); + + /** + * From the provides set of resources, return those that match the enablement + * rule specified for the model provider descriptor. The resource mappings + * for the returned resources can then be obtained by invoking + * {@link ModelProvider#getMappings(IResource[], ResourceMappingContext, IProgressMonitor)} + * + * @param resources the resources + * @return the resources that match the descriptor's enablement rule + */ + public IResource[] getMatchingResources(IResource[] resources) throws CoreException; + + /** + * Return the set of traversals that overlap with the resources that + * this descriptor matches. + * + * @param traversals the traversals being tested + * @return the subset of these traversals that overlap with the resources + * that match this descriptor + * @throws CoreException + */ + public ResourceTraversal[] getMatchingTraversals(ResourceTraversal[] traversals) throws CoreException; + + /** + * Return the model provider for this descriptor, instantiating it if it is + * the first time the method is called. + * + * @return the model provider for this descriptor + * @exception CoreException if the model provider could not be instantiated for + * some reason + */ + public ModelProvider getModelProvider() throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java new file mode 100644 index 0000000000..c1a64de33e --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 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.mapping; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * This factory is used to build a resource delta that represents a proposed change + * that can then be passed to the {@link ResourceChangeValidator#validateChange(IResourceDelta, IProgressMonitor)} + * method in order to validate the change with any model providers stored in those resources. + * The deltas created by calls to the methods of this interface will be the same as + * those generated by the workspace if the proposed operations were performed. + *

          + * This factory does not validate that the proposed operation is valid given the current + * state of the resources and any other proposed changes. It only records the + * delta that would result. + * + * @see ResourceChangeValidator + * @see ModelProvider + * @since 3.2 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceChangeDescriptionFactory { + + /** + * Record a delta that represents a content change for the given file. + * @param file the file whose contents will be changed + */ + public void change(IFile file); + + /** + * Record the set of deltas representing the closed of a project. + * @param project the project that will be closed + */ + public void close(IProject project); + + /** + * Record the set of deltas representing a copy of the given resource to the + * given workspace path. + * @param resource the resource that will be copied + * @param destination the full workspace path of the destination the resource is being copied to + */ + public void copy(IResource resource, IPath destination); + + /** + * Record a delta that represents a resource being created. + * @param resource the resource that is created + */ + public void create(IResource resource); + + /** + * Record the set of deltas representing a deletion of the given resource. + * @param resource the resource that will be deleted + */ + public void delete(IResource resource); + + /** + * Return the proposed delta that has been accumulated by this factory. + * @return the proposed delta that has been accumulated by this factory + */ + public IResourceDelta getDelta(); + + /** + * Record the set of deltas representing a move of the given resource to the + * given workspace path. Note that this API is used to describe a resource + * being moved to another path in the workspace, rather than a move in the + * file system. + * @param resource the resource that will be moved + * @param destination the full workspace path of the destination the resource is being moved to + */ + public void move(IResource resource, IPath destination); + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java new file mode 100644 index 0000000000..ed0cfee358 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.*; +import org.eclipse.core.internal.resources.mapping.ModelProviderManager; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * Represents the provider of a logical model. The main purpose of this + * API is to support batch operations on sets of ResourceMapping + * objects that are part of the same model. + * + *

          + * This class may be subclassed by clients. + *

          + * @see org.eclipse.core.resources.mapping.ResourceMapping + * @since 3.2 + */ +public abstract class ModelProvider extends PlatformObject { + + /** + * The model provider id of the Resources model. + */ + public static final String RESOURCE_MODEL_PROVIDER_ID = "org.eclipse.core.resources.modelProvider"; //$NON-NLS-1$ + + private IModelProviderDescriptor descriptor; + + /** + * Return the descriptor for the model provider of the given id + * or null if the provider has not been registered. + * @param id a model provider id. + * @return the descriptor for the model provider of the given id + * or null if the provider has not been registered + */ + public static IModelProviderDescriptor getModelProviderDescriptor(String id) { + IModelProviderDescriptor[] descs = ModelProviderManager.getDefault().getDescriptors(); + for (int i = 0; i < descs.length; i++) { + IModelProviderDescriptor descriptor = descs[i]; + if (descriptor.getId().equals(id)) { + return descriptor; + } + } + return null; + } + + /** + * Return the descriptors for all model providers that are registered. + * + * @return the descriptors for all model providers that are registered. + */ + public static IModelProviderDescriptor[] getModelProviderDescriptors() { + return ModelProviderManager.getDefault().getDescriptors(); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof ModelProvider) { + ModelProvider other = (ModelProvider) obj; + return other.getDescriptor().getId().equals(getDescriptor().getId()); + } + return super.equals(obj); + } + + /** + * Return the descriptor of this model provider. The descriptor + * is set during initialization so implements cannot call this method + * until after the initialize method is invoked. + * @return the descriptor of this model provider + */ + public final IModelProviderDescriptor getDescriptor() { + return descriptor; + } + + /** + * Returns the unique identifier of this model provider. + *

          + * The model provider identifier is composed of the model provider's + * plug-in id and the simple id of the provider extension. For example, if + * plug-in "com.xyz" defines a provider extension with id + * "myModelProvider", the unique model provider identifier will be + * "com.xyz.myModelProvider". + *

          + * + * @return the unique model provider identifier + */ + public final String getId() { + return descriptor.getId(); + } + + /** + * Return the resource mappings that cover the given resource. + * By default, an empty array is returned. Subclass may override + * this method but should consider overriding either + * {@link #getMappings(IResource[], ResourceMappingContext, IProgressMonitor)} + * or {@link #getMappings(ResourceTraversal[], ResourceMappingContext, IProgressMonitor)} + * if more context is needed to determine the proper mappings. + * + * @param resource the resource + * @param context a resource mapping context + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the resource mappings that cover the given resource. + * @exception CoreException + */ + public ResourceMapping[] getMappings(IResource resource, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + return new ResourceMapping[0]; + } + + /** + * Return the set of mappings that cover the given resources. + * This method is used to map operations on resources to + * operations on resource mappings. By default, this method + * calls getMapping(IResource) for each resource. + *

          + * Subclasses may override this method. + *

          + * + * @param resources the resources + * @param context a resource mapping context + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the set of mappings that cover the given resources + * @exception CoreException + */ + public ResourceMapping[] getMappings(IResource[] resources, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + Set mappings = new HashSet(); + for (int i = 0; i < resources.length; i++) { + IResource resource = resources[i]; + ResourceMapping[] resourceMappings = getMappings(resource, context, monitor); + if (resourceMappings.length > 0) + mappings.addAll(Arrays.asList(resourceMappings)); + } + return mappings.toArray(new ResourceMapping[mappings.size()]); + } + + /** + * Return the set of mappings that overlap with the given resource traversals. + * This method is used to map operations on resources to + * operations on resource mappings. By default, this method + * calls {@link #getMappings(IResource[], ResourceMappingContext, IProgressMonitor)} + * with the resources extracted from each traversal. + *

          + * Subclasses may override this method. + *

          + * + * @param traversals the traversals + * @param context a resource mapping context + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the set of mappings that overlap with the given resource traversals + */ + public ResourceMapping[] getMappings(ResourceTraversal[] traversals, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + Set result = new HashSet(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + ResourceMapping[] mappings = getMappings(traversal.getResources(), context, monitor); + for (int j = 0; j < mappings.length; j++) + result.add(mappings[j]); + } + return result.toArray(new ResourceMapping[result.size()]); + } + + /** + * Return a set of traversals that cover the given resource mappings. The + * provided mappings must be from this provider or one of the providers this + * provider extends. + *

          + * The default implementation accumulates the traversals from the given + * mappings. Subclasses can override to provide a more optimal + * transformation. + *

          + * + * @param mappings the mappings being mapped to resources + * @param context the context used to determine the set of traversals that + * cover the mappings + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a set of traversals that cover the given mappings + * @exception CoreException + */ + public ResourceTraversal[] getTraversals(ResourceMapping[] mappings, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + try { + monitor.beginTask("", 100 * mappings.length); //$NON-NLS-1$ + List traversals = new ArrayList(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + traversals.addAll(Arrays.asList(mapping.getTraversals(context, new SubProgressMonitor(monitor, 100)))); + } + return traversals.toArray(new ResourceTraversal[traversals.size()]); + } finally { + monitor.done(); + } + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return getDescriptor().getId().hashCode(); + } + + /** + * This method is called by the model provider framework when the model + * provider is instantiated. This method should not be called by clients and + * cannot be overridden by subclasses. However, it invokes the + * initialize method once the descriptor is set so subclasses + * can override that method if they need to do additional initialization. + * + * @param desc the description of the provider as it appears in the plugin manifest + * @noreference This method is not intended to be referenced by clients. + */ + public final void init(IModelProviderDescriptor desc) { + if (descriptor != null) + // prevent subsequent calls from damaging this instance + return; + descriptor = desc; + initialize(); + } + + /** + * Initialization method that is called after the descriptor + * of this provider is set. Subclasses may override. + */ + protected void initialize() { + // Do nothing + } + + /** + * Validate the proposed changes contained in the given delta. + *

          + * This method must return either a {@link ModelStatus}, or a {@link MultiStatus} + * whose children are {@link ModelStatus}. The severity of the returned status + * indicates the severity of the possible side-effects of the operation. Any + * severity other than OK will be shown to the user. The + * message should be a human readable message that will allow the user to + * make a decision on whether to continue with the operation. The model + * provider id should indicate which model is flagging the possible side effects. + *

          + * This default implementation accepts all changes and returns a status with + * severity OK. Subclasses should override to perform + * validation specific to their model. + *

          + * + * @param delta a delta tree containing the proposed changes + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status indicating any potential side effects + * on the model that provided this validator. + */ + public IStatus validateChange(IResourceDelta delta, IProgressMonitor monitor) { + return new ModelStatus(IStatus.OK, ResourcesPlugin.PI_RESOURCES, descriptor.getId(), Status.OK_STATUS.getMessage()); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java new file mode 100644 index 0000000000..bd17d44f02 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 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.resources.mapping; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.Status; + +/** + * A status returned by a model from the resource operation validator. + * The severity indicates the severity of the possible side effects + * of the operation. Any severity other than OK should be + * shown to the user. The message should be a human readable message that + * will allow the user to make a decision as to whether to continue with the + * operation. The model provider id should indicate which model is flagging the + * the possible side effects. + *

          + * Clients may instantiate or subclass this class. + *

          + * + * @since 3.2 + */ +public class ModelStatus extends Status { + + private final String modelProviderId; + + /** + * Create a model status. + * + * @param severity the severity + * @param pluginId the plugin id + * @param modelProviderId the model provider id + * @param message the message + */ + public ModelStatus(int severity, String pluginId, String modelProviderId, String message) { + super(severity, pluginId, 0, message, null); + Assert.isNotNull(modelProviderId); + this.modelProviderId = modelProviderId; + } + + /** + * Return the id of the model provider from which this status originated. + * + * @return the id of the model provider from which this status originated + */ + public String getModelProviderId() { + return modelProviderId; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java new file mode 100644 index 0000000000..ec47946248 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java @@ -0,0 +1,321 @@ +/******************************************************************************* + * 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.resources.mapping; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A remote mapping context provides a model element with a view of the remote + * state of local resources as they relate to a repository operation that is in + * progress. A repository provider can pass an instance of this interface to a + * model element when obtaining a set of traversals for a model element. This + * allows the model element to query the remote state of a resource in order to + * determine if there are resources that exist remotely but do not exist locally + * that should be included in the traversal. + *

          + * This class may be subclassed by clients. + *

          + * + * @see ResourceMapping + * @see ResourceMappingContext + * @since 3.2 + */ +public abstract class RemoteResourceMappingContext extends ResourceMappingContext { + + /** + * Refresh flag constant (bit mask value 1) indicating that the mapping will + * be making use of the contents of the files covered by the traversals + * being refreshed. + */ + public static final int FILE_CONTENTS_REQUIRED = 1; + + /** + * Refresh flag constant (bit mask value 0) indicating that no additional + * refresh behavior is required. + */ + public static final int NONE = 0; + + /** + * For three-way comparisons, returns an instance of IStorage in order to + * allow the caller to access the contents of the base resource that + * corresponds to the given local resource. The base of a resource is the + * contents of the resource before any local modifications were made. If the + * base file does not exist, or if this is a two-way comparison, null + * is returned. The provided local file handle need not exist locally. A exception + * is thrown if the corresponding base resource is not a file. + *

          + * This method may be long running as a server may need to be contacted to + * obtain the contents of the file. + *

          + * + * @param file the local file + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a storage that provides access to the contents of the local + * resource's corresponding remote resource. If the remote file does not + * exist, null is returned + * @exception CoreException if the contents could not be fetched. Reasons + * include: + *
            + *
          • The server could not be contacted for some reason. + *
          • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
          • + *
          + */ + public abstract IStorage fetchBaseContents(IFile file, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the members of the base resource corresponding to the given container. + * The container and the returned members need not exist locally and may not + * include all children that exist locally. An empty list is returned if the base resource + * is empty or does not exist. An exception is thrown if the base resource is not + * capable of having members. This method returns null if + * the base members cannot be computed, in which case clients should call + * {@link #fetchMembers(IContainer, IProgressMonitor)} which returns the + * combined members for the base and remote. + *

          + *

          + * This method may be long running as a server may need to be contacted to + * obtain the members of the base resource. + *

          + *

          + * This default implementation always returns null, but subclasses + * may override. + *

          + * @param container the local container + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the members of the base resource corresponding to the given container + * @exception CoreException if the members could not be fetched. Reasons + * include: + *
            + *
          • The server could not be contacted for some reason.
          • + *
          • The base resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
          • + *
          + * @since 3.3 + */ + public IResource[] fetchBaseMembers(IContainer container, IProgressMonitor monitor) throws CoreException { + //default implementation does nothing + //thwart compiler warning + return null; + } + + /** + * Returns the combined members of the base and remote resources corresponding + * to the given container. The container need not exist locally and the result may + * include entries that do not exist locally and may not include all local + * children. An empty list is returned if the remote resource which + * corresponds to the container is empty or if the remote does not exist. An + * exception is thrown if the corresponding remote is not capable of having + * members. + *

          + * This method may be long running as a server may need to be contacted to + * obtain the members of the container's corresponding remote resource. + *

          + * + * @param container the local container + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return returns the combined members of the base and remote resources + * corresponding to the given container. + * @exception CoreException if the members could not be fetched. Reasons + * include: + *
            + *
          • The server could not be contacted for some reason.
          • + *
          • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
          • + *
          + */ + public abstract IResource[] fetchMembers(IContainer container, IProgressMonitor monitor) throws CoreException; + + /** + * Returns an instance of IStorage in order to allow the caller to access + * the contents of the remote that corresponds to the given local resource. + * If the remote file does not exist, null is returned. The + * provided local file handle need not exist locally. A exception is thrown + * if the corresponding remote resource is not a file. + *

          + * This method may be long running as a server may need to be contacted to + * obtain the contents of the file. + *

          + * + * @param file the local file + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a storage that provides access to the contents of the local + * resource's corresponding remote resource. If the remote file does not + * exist, null is returned + * @exception CoreException if the contents could not be fetched. Reasons + * include: + *
            + *
          • The server could not be contacted for some reason.
          • + *
          • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
          • + *
          + */ + public abstract IStorage fetchRemoteContents(IFile file, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the members of the remote resource corresponding to the given container. + * The container and the returned members need not exist locally and may not + * include all children that exist locally. An empty list is returned if the remote resource + * is empty or does not exist. An exception is thrown if the remote resource is not + * capable of having members. This method returns null if + * the remote members cannot be computed, in which case clients should call + * {@link #fetchMembers(IContainer, IProgressMonitor)} which returns the + * combined members for the base and remote. + *

          + *

          + * This method may be long running as a server may need to be contacted to + * obtain the members of the remote resource. + *

          + *

          + * This default implementation always returns null, but subclasses + * may override. + *

          + * @param container the local container + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the members of the remote resource corresponding to the given container + * @exception CoreException if the members could not be fetched. Reasons + * include: + *
            + *
          • The server could not be contacted for some reason.
          • + *
          • The remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
          • + *
          + * @since 3.3 + */ + public IResource[] fetchRemoteMembers(IContainer container, IProgressMonitor monitor) throws CoreException { + //default implementation does nothing + //thwart compiler warning + return null; + } + + /** + * Return the list of projects that apply to this context. + * In other words, the context is only capable of querying the + * remote state for projects that are contained in the + * returned list. + * @return the list of projects that apply to this context + */ + public abstract IProject[] getProjects(); + + /** + * For three-way comparisons, this method indicates whether local + * modifications have been made to the given resource. For two-way + * comparisons, calling this method has the same effect as calling + * {@link #hasRemoteChange(IResource, IProgressMonitor)}. + * + * @param resource the resource being tested + * @param monitor a progress monitor + * @return whether the resource contains local modifications + * @exception CoreException if the contents could not be compared. Reasons + * include: + *
            + *
          • The server could not be contacted for some reason.
          • + *
          • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
          • + *
          + */ + public abstract boolean hasLocalChange(IResource resource, IProgressMonitor monitor) throws CoreException; + + /** + * For two-way comparisons, return whether the contents of the corresponding + * remote differs from the content of the local file in the context of the + * current operation. By this we mean that this method will return + * true if the remote contents differ from the local + * contents. + *

          + * For three-way comparisons, return whether the contents of the + * corresponding remote differ from the contents of the base. In other + * words, this method returns true if the corresponding + * remote has changed since the last time the local resource was updated + * with the remote contents. + *

          + * For two-way comparisons, return true if the remote + * contents differ from the local contents. In this case, this method is + * equivalent to {@link #hasLocalChange(IResource, IProgressMonitor)} + *

          + * This can be used by clients to determine if they need to fetch the remote + * contents in order to determine if the resources that constitute the model + * element are different in the remote location. If the local file exists + * and the remote file does not, or the remote file exists and the local + * does not then the contents will be said to differ (i.e. true + * is returned). Also, implementors will most likely use a timestamp based + * comparison to determine if the contents differ. This may lead to a + * situation where true is returned but the actual contents + * do not differ. Clients must be prepared handle this situation. + *

          + * + * @param resource the local resource + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return whether the contents of the corresponding remote differ from the + * base. + * @exception CoreException if the contents could not be compared. Reasons + * include: + *
            + *
          • The server could not be contacted for some reason.
          • + *
          • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
          • + *
          + */ + public abstract boolean hasRemoteChange(IResource resource, IProgressMonitor monitor) throws CoreException; + + /** + * Return true if the context is associated with an operation + * that is using a three-way comparison and false if it is + * using a two-way comparison. + * + * @return whether the context is a three-way or two-way + */ + public abstract boolean isThreeWay(); + + /** + * Refresh the known remote state for any resources covered by the given + * traversals. Clients who require the latest remote state should invoke + * this method before invoking any others of the class. Mappings can use + * this method as a hint to the context provider of which resources will be + * required for the mapping to generate the proper set of traversals. + *

          + * Note that this is really only a hint to the context provider. It is up to + * implementors to decide, based on the provided traversals, how to + * efficiently perform the refresh. In the ideal case, calls to + * {@link #hasRemoteChange(IResource, IProgressMonitor)} and + * {@link #fetchMembers} would not need to contact the server after a call to a + * refresh with appropriate traversals. Also, ideally, if + * {@link #FILE_CONTENTS_REQUIRED} is on of the flags, then the contents + * for these files will be cached as efficiently as possible so that calls to + * {@link #fetchRemoteContents} will also not need to contact the server. This + * may not be possible for all context providers, so clients cannot assume that + * the above mentioned methods will not be long running. It is still advisable + * for clients to call {@link #refresh} with as much details as possible since, in + * the case where a provider is optimized, performance will be much better. + *

          + * + * @param traversals the resource traversals that indicate which resources + * are to be refreshed + * @param flags additional refresh behavior. For instance, if + * {@link #FILE_CONTENTS_REQUIRED} is one of the flags, this indicates + * that the client will be accessing the contents of the files covered by + * the traversals. {@link #NONE} should be used when no additional + * behavior is required + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the refresh fails. Reasons include: + *
            + *
          • The server could not be contacted for some reason.
          • + *
          + */ + public abstract void refresh(ResourceTraversal[] traversals, int flags, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java new file mode 100644 index 0000000000..6fa298a038 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.resources.mapping; + +import java.util.ArrayList; +import java.util.List; +import org.eclipse.core.internal.resources.mapping.ChangeDescription; +import org.eclipse.core.internal.resources.mapping.ResourceChangeDescriptionFactory; +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 resource change validator is used to validate that changes made to + * resources will not adversely affect the models stored in those resources. + *

          + * The validator is used by first creating a resource delta describing the + * proposed changes. A delta can be generated using a {@link IResourceChangeDescriptionFactory}. + * The change is then validated by calling the {@link #validateChange(IResourceDelta, IProgressMonitor)} + * method. This example validates a change to a single file: + * + * IFile file = ..;//some file that is going to be changed + * ResourceChangeValidator validator = ResourceChangeValidator.getValidator(); + * IResourceChangeDescriptionFactory factory = validator.createDeltaFactory(); + * factory.change(file); + * IResourceDelta delta = factory.getDelta(); + * IStatus result = validator.validateChange(delta, null); + * + * If the result status does not have severity {@link IStatus#OK}, then + * the changes may cause problems for models that are built on those + * resources. In this case the user should be presented with the status message + * to determine if they want to proceed with the modification. + *

          + * + * @since 3.2 + */ +public final class ResourceChangeValidator { + private static ResourceChangeValidator instance; + + /** + * Return the singleton change validator. + * @return the singleton change validator + */ + public static ResourceChangeValidator getValidator() { + if (instance == null) + instance = new ResourceChangeValidator(); + return instance; + } + + /** + * Singleton accessor method should be used instead. + * @see #getValidator() + */ + private ResourceChangeValidator() { + super(); + } + + private IStatus combineResults(IStatus[] result) { + List notOK = new ArrayList(); + for (int i = 0; i < result.length; i++) { + IStatus status = result[i]; + if (!status.isOK()) { + notOK.add(status); + } + } + if (notOK.isEmpty()) { + return Status.OK_STATUS; + } + if (notOK.size() == 1) { + return notOK.get(0); + } + return new MultiStatus(ResourcesPlugin.PI_RESOURCES, 0, notOK.toArray(new IStatus[notOK.size()]), Messages.mapping_multiProblems, null); + } + + /** + * Return an empty change description factory that can be used to build a + * proposed resource delta. + * @return an empty change description factory that can be used to build a + * proposed resource delta + */ + public IResourceChangeDescriptionFactory createDeltaFactory() { + return new ResourceChangeDescriptionFactory(); + } + + private ModelProvider[] getProviders(IResource[] resources) { + IModelProviderDescriptor[] descriptors = ModelProvider.getModelProviderDescriptors(); + List result = new ArrayList(); + for (int i = 0; i < descriptors.length; i++) { + IModelProviderDescriptor descriptor = descriptors[i]; + try { + IResource[] matchingResources = descriptor.getMatchingResources(resources); + if (matchingResources.length > 0) { + result.add(descriptor.getModelProvider()); + } + } catch (CoreException e) { + Policy.log(e.getStatus().getSeverity(), NLS.bind("Could not instantiate provider {0}", descriptor.getId()), e); //$NON-NLS-1$ + } + } + return result.toArray(new ModelProvider[result.size()]); + } + + /* + * Get the roots of any changes. + */ + private IResource[] getRootResources(IResourceDelta root) { + final ChangeDescription changeDescription = new ChangeDescription(); + try { + root.accept(new IResourceDeltaVisitor() { + @Override + public boolean visit(IResourceDelta delta) { + return changeDescription.recordChange(delta); + } + }); + } catch (CoreException e) { + // Shouldn't happen since the ProposedResourceDelta accept doesn't throw an + // exception and our visitor doesn't either + Policy.log(IStatus.ERROR, "Internal error", e); //$NON-NLS-1$ + } + return changeDescription.getRootResources(); + } + + /** + * Validate the proposed changes contained in the given delta + * by consulting all model providers to determine if the changes + * have any adverse side effects. + *

          + * This method returns either a {@link ModelStatus}, or a {@link MultiStatus} + * whose children are {@link ModelStatus}. In either case, the severity + * of the status indicates the severity of the possible side-effects of + * the operation. Any severity other than OK should be + * shown to the user. The message should be a human readable message that + * will allow the user to make a decision on whether to continue with the + * operation. The model provider id should indicate which model is flagging the + * the possible side effects. + *

          + * + * @param delta a delta tree containing the proposed changes + * @return a status indicating any potential side effects + * on models stored in the affected resources. + */ + public IStatus validateChange(IResourceDelta delta, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + IResource[] resources = getRootResources(delta); + ModelProvider[] providers = getProviders(resources); + if (providers.length == 0) + return Status.OK_STATUS; + monitor.beginTask(Messages.mapping_validate, providers.length); + IStatus[] result = new IStatus[providers.length]; + for (int i = 0; i < providers.length; i++) + result[i] = providers[i].validateChange(delta, Policy.subMonitorFor(monitor, 1)); + return combineResults(result); + } finally { + monitor.done(); + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java new file mode 100644 index 0000000000..ed2e21c265 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java @@ -0,0 +1,204 @@ +/******************************************************************************* + * 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 Corporation - initial API and implementation + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.ArrayList; +import org.eclipse.core.internal.resources.mapping.ModelProviderManager; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * A resource mapping supports the transformation of an application model + * object into its underlying file system resources. It provides the + * bridge between a logical element and the physical resource(s) into which it + * is stored but does not provide more comprehensive model access or + * manipulations. + *

          + * Mappings provide two means of model traversal. The {@link #accept} method + * can be used to visit the resources that constitute the model object. Alternatively, + * a set or traversals can be obtained by calling {@link #getTraversals}. A traversal + * contains a set of resources and a depth. This allows clients (such a repository providers) + * to do optimal traversals of the resources w.r.t. the operation that is being performed + * on the model object. + *

          + *

          + * This class may be subclassed by clients. + *

          + + * @see IResource + * @see ResourceTraversal + * @since 3.2 + */ +public abstract class ResourceMapping extends PlatformObject { + + /** + * Accepts the given visitor for all existing resources in this mapping. + * The visitor's {@link IResourceVisitor#visit} method is called for each + * accessible resource in this mapping. + * + * @param context the traversal context + * @param visitor the visitor + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
            + *
          • The visitor failed with this exception.
          • + *
          • The traversals for this mapping could not be obtained.
          • + *
          + */ + public void accept(ResourceMappingContext context, IResourceVisitor visitor, IProgressMonitor monitor) throws CoreException { + ResourceTraversal[] traversals = getTraversals(context, monitor); + for (int i = 0; i < traversals.length; i++) + traversals[i].accept(visitor); + } + + /** + * Return whether this resource mapping contains all the resources + * of the given mapping. + *

          + * This method always returns false when the given resource + * mapping's model provider id does not match that the of the receiver. + *

          + * + * @param mapping the given resource mapping + * @return true if this mapping contains all the resources + * of the given mapping, and false otherwise. + */ + public boolean contains(ResourceMapping mapping) { + return false; + } + + /** + * Override equals to compare the model objects of the + * mapping in order to determine equality. + * @param obj the object to compare + * @return true if the receiver is equal to the + * given object, and false otherwise. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj instanceof ResourceMapping) { + ResourceMapping other = (ResourceMapping) obj; + return other.getModelObject().equals(getModelObject()); + } + return false; + } + + /** + * Returns all markers of the specified type on the resources in this mapping. + * If includeSubtypes is false, only markers + * whose type exactly matches the given type are returned. Returns an empty + * array if there are no matching markers. + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return an array of markers + * @exception CoreException if this method fails. + */ + public IMarker[] findMarkers(String type, boolean includeSubtypes, IProgressMonitor monitor) throws CoreException { + final ResourceTraversal[] traversals = getTraversals(ResourceMappingContext.LOCAL_CONTEXT, monitor); + ArrayList result = new ArrayList(); + for (int i = 0; i < traversals.length; i++) + traversals[i].doFindMarkers(result, type, includeSubtypes); + return result.toArray(new IMarker[result.size()]); + } + + /** + * Returns the application model element associated with this + * resource mapping. + * + * @return the application model element associated with this + * resource mapping. + */ + public abstract Object getModelObject(); + + /** + * Return the model provider for the model object + * of this resource mapping. The model provider is obtained + * using the id returned from getModelProviderId(). + * @return the model provider + */ + public final ModelProvider getModelProvider() { + try { + return ModelProviderManager.getDefault().getModelProvider(getModelProviderId()); + } catch (CoreException e) { + throw new IllegalStateException(e.getMessage()); + } + } + + /** + * Returns the id of the model provider that generated this resource + * mapping. + * + * @return the model provider id + */ + public abstract String getModelProviderId(); + + /** + * Returns the projects that contain the resources that constitute this + * application model. + * + * @return the projects + */ + public abstract IProject[] getProjects(); + + /** + * Returns one or more traversals that can be used to access all the + * physical resources that constitute the logical resource. A traversal is + * simply a set of resources and the depth to which they are to be + * traversed. This method returns an array of traversals in order to provide + * flexibility in describing the traversals that constitute a model element. + *

          + * Subclasses should, when possible, include + * all resources that are or may be members of the model element. + * For instance, a model element should return the same list of + * resources regardless of the existence of the files on the file system. + * For example, if a logical resource called "form" maps to "/p1/form.xml" + * and "/p1/form.java" then whether form.xml or form.java existed, they + * should be returned by this method. + *

          + * In some cases, it may not be possible for a model element to know all the + * resources that may constitute the element without accessing the state of + * the model element in another location (e.g. a repository). This method is + * provided with a context which, when provided, gives access to + * the members of corresponding remote containers and the contents of + * corresponding remote files. This gives the model element the opportunity + * to deduce what additional resources should be included in the traversal. + *

          + * + * @param context gives access to the state of + * remote resources that correspond to local resources for the + * purpose of determining traversals that adequately cover the + * model element resources given the state of the model element + * in another location. This parameter may be null, in + * which case the implementor can assume that only the local + * resources are of interest to the client. + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a set of traversals that cover the resources that constitute the + * model element + * @exception CoreException if the traversals could not be obtained. + */ + public abstract ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) throws CoreException; + + /** + * Override hashCode to use the model object. + */ + @Override + public int hashCode() { + return getModelObject().hashCode(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java new file mode 100644 index 0000000000..13d737faa2 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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 Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +/** + * A resource mapping context is provided to a resource mapping when traversing + * the resources of the mapping. The type of context may determine what resources + * are included in the traversals of a mapping. + *

          + * There are currently two resource mapping contexts: the local mapping context + * (represented by the singleton {@link #LOCAL_CONTEXT}), + * and {@link RemoteResourceMappingContext}. Implementors of {@link ResourceMapping} + * should not assume that these are the only valid contexts (in order to allow future + * extensibility). Therefore, if the provided context is not of one of the above mentioned types, + * the implementor can assume that the context is a local context. + *

          + *

          + * This class may be subclassed by clients; this class is not intended to be + * instantiated directly. + *

          + * + * @see ResourceMapping + * @see RemoteResourceMappingContext + * @since 3.2 + */ +public class ResourceMappingContext { + + /** + * This resource mapping context is used to indicate that the operation + * that is requesting the traversals is performing a local operation. + * Because the operation is local, the resource mapping is free to be + * as precise as desired about what resources make up the mapping without + * concern for performing optimized remote operations. + */ + public static final ResourceMappingContext LOCAL_CONTEXT = new ResourceMappingContext(); + +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java new file mode 100644 index 0000000000..220a22d2d9 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java @@ -0,0 +1,182 @@ +/******************************************************************************* + * 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.resources.mapping; + +import java.util.ArrayList; +import org.eclipse.core.internal.resources.MarkerManager; +import org.eclipse.core.internal.resources.Workspace; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; + +/** + * A resource traversal is simply a set of resources and the depth to which + * each is to be traversed. A set of traversals is used to describe the + * resources that constitute a model element. + *

          + * The flags of the traversal indicate which special resources should be + * included or excluded from the traversal. The flags used are the same as + * those passed to the {@link IResource#accept(IResourceVisitor, int, int)} method. + * + *

          + * This class may be instantiated or subclassed by clients. + *

          + + * @see org.eclipse.core.resources.IResource + * @since 3.2 + */ +public class ResourceTraversal { + + private final int depth; + private final int flags; + private final IResource[] resources; + + /** + * Creates a new resource traversal. + * @param resources The resources in the traversal + * @param depth The traversal depth + * @param flags the flags for this traversal. The traversal flags match those + * that are passed to the IResource#accept method. + */ + public ResourceTraversal(IResource[] resources, int depth, int flags) { + if (resources == null) + throw new NullPointerException(); + this.resources = resources; + this.depth = depth; + this.flags = flags; + } + + /** + * Visits all existing resources defined by this traversal. + * + * @param visitor a resource visitor + * @exception CoreException if this method fails. Reasons include: + *
            + *
          • The visitor failed with this exception.
          • + *
          + */ + public void accept(IResourceVisitor visitor) throws CoreException { + for (int i = 0, imax = resources.length; i < imax; i++) + try { + if (resources[i].exists()) + resources[i].accept(visitor, depth, flags); + } catch (CoreException e) { + //ignore failure in the case of concurrent deletion + if (e.getStatus().getCode() != IResourceStatus.RESOURCE_NOT_FOUND) + throw e; + } + } + + /** + * Return whether the given resource is contained in or + * covered by this traversal, regardless of whether the resource + * currently exists. + * + * @param resource the resource to be tested + * @return true if the resource is in this traversal, and + * false otherwise. + */ + public boolean contains(IResource resource) { + for (int i = 0; i < resources.length; i++) { + IResource member = resources[i]; + if (contains(member, resource)) { + return true; + } + } + return false; + } + + private boolean contains(IResource resource, IResource child) { + if (resource.equals(child)) + return true; + if (depth == IResource.DEPTH_ZERO) + return false; + if (child.getParent().equals(resource)) + return true; + if (depth == IResource.DEPTH_INFINITE) + return resource.getFullPath().isPrefixOf(child.getFullPath()); + return false; + } + + /** + * Efficient implementation of {@link #findMarkers(String, boolean)}, not + * available to clients because underlying non-API methods are used that + * may change. + */ + void doFindMarkers(ArrayList result, String type, boolean includeSubtypes) { + MarkerManager markerMan = ((Workspace) ResourcesPlugin.getWorkspace()).getMarkerManager(); + for (int i = 0; i < resources.length; i++) + markerMan.doFindMarkers(resources[i], result, type, includeSubtypes, depth); + } + + /** + * Returns all markers of the specified type on existing resources in this traversal. + * If includeSubtypes is false, only markers + * whose type exactly matches the given type are returned. Returns an empty + * array if there are no matching markers. + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @return an array of markers + * @exception CoreException if this method fails. + * @see IResource#findMarkers(String, boolean, int) + */ + public IMarker[] findMarkers(String type, boolean includeSubtypes) throws CoreException { + if (resources.length == 0) + return new IMarker[0]; + ArrayList result = new ArrayList(); + doFindMarkers(result, type, includeSubtypes); + return result.toArray(new IMarker[result.size()]); + } + + /** + * Returns the depth to which the resources should be traversed. + * + * @return the depth to which the physical resources are to be traversed + * (one of IResource.DEPTH_ZERO, IResource.DEPTH_ONE or + * IResource.DEPTH_INFINITE) + */ + public int getDepth() { + return depth; + } + + /** + * Return the flags for this traversal. + * The flags of the traversal indicate which special resources should be + * included or excluded from the traversal. The flags used are the same as + * those passed to the IResource#accept(IResourceVisitor, int, int) method. + * Clients who traverse the resources manually (i.e. without calling accept) + * should respect the flags when determining which resources are included + * in the traversal. + * + * @return the flags for this traversal + */ + public int getFlags() { + return flags; + } + + /** + * Returns the file system resource(s) for this traversal. The returned + * resources must be contained within the same project and need not exist in + * the local file system. The traversal of the returned resources should be + * done considering the flag returned by getDepth. If a resource returned by + * a traversal is a file, it should always be visited. If a resource of a + * traversal is a folder then files contained in the folder can only be + * visited if the folder is IResource.DEPTH_ONE or IResource.DEPTH_INFINITE. + * Child folders should only be visited if the depth is + * IResource.DEPTH_INFINITE. + * + * @return The resources in this traversal + */ + public IResource[] getResources() { + return resources; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html new file mode 100644 index 0000000000..c3214612a1 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html @@ -0,0 +1,27 @@ + + + + + + + Package-level Javadoc + + +Provides APIs for integrating application models with the workspace + +

          Package Specification

          +

          +This package specifies the APIs in the Resources plug-in that are used to integrate +application models with the workspace. This API introduces the notion of a +ResourceMapping that defines the relationship between an application +model object and a set of underlying resources, and a ResourceTraversal +that describes the exact resources corresponding to a given application model object. +The relationship between an application model and underlying resources can vary +depending a context. This notion is captured by ResourceMappingContext +and its subclasses. +

          +@since 3.2 +

          + + + diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html new file mode 100644 index 0000000000..6fe0b74733 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html @@ -0,0 +1,25 @@ + + + + + + + Package-level Javadoc + + +Provides basic support for managing a workspace and +its resources. +

          +Package Specification

          +This package specifies the principal API for the Resources plug-in.  +The resources plug-in defines the notions of Workspaces and Resources.  +The workspace's resource model is very similar to a file system.  +All resources are backed by a real file or directory in some backing file +system.  They are stored in their native form (i.e., no extra bytes +or markup) using their normal names. +

          In addition to basic resource management, the Resources plug-in supports +various workspace lifecycle events such as save and snapshot, and resource +change events. +
            + + diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java new file mode 100644 index 0000000000..53000df403 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 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.resources.refresh; + +import org.eclipse.core.resources.IResource; + +/** + * An IRefreshMonitor monitors trees of IResources + * for changes in the local file system. + *

          + * When an IRefreshMonitor notices changes, it should report them + * to the IRefreshResult provided at the time of the monitor's + * creation. + *

          + * Clients may implement this interface. + *

          + * + * @since 3.0 + */ +public interface IRefreshMonitor { + /** + * Informs the monitor that it should stop monitoring the given resource. + * + * @param resource the resource that should no longer be monitored, or + * null if this monitor should stop monitoring all resources + * it is currently monitoring + */ + public void unmonitor(IResource resource); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java new file mode 100644 index 0000000000..1e78cacb8e --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 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.resources.refresh; + +import org.eclipse.core.resources.IResource; + +/** + * An IRefreshResult is provided to an auto-refresh + * monitor. The result is used to submit resources to be refreshed, and + * for reporting failure of the monitor. + * + * @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 IRefreshResult { + /** + * Notifies that the given monitor has encountered a failure from which it + * cannot recover while monitoring the given resource. + *

          + * If the given resource is null it indicates that the + * monitor has failed completely, and the refresh manager will have to + * take over the monitoring responsibilities for all resources that the + * monitor was monitoring. + * + * @param monitor a monitor which has encountered a failure that it + * cannot recover from + * @param resource the resource that the monitor can no longer + * monitor, or null to indicate that the monitor can no + * longer monitor any of the resources it was monitoring + */ + public void monitorFailed(IRefreshMonitor monitor, IResource resource); + + /** + * Requests that the provided resource be refreshed. The refresh will + * occur in the background during the next scheduled refresh. + * + * @param resource the resource to refresh + */ + public void refresh(IResource resource); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java new file mode 100644 index 0000000000..0ed3cd8784 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.resources.refresh; + +import org.eclipse.core.internal.refresh.InternalRefreshProvider; +import org.eclipse.core.resources.IResource; + +/** + * The abstract base class for all auto-refresh providers. This class provides + * the infrastructure for defining an auto-refresh provider and fulfills the + * contract specified by the org.eclipse.core.resources.refreshProviders + * standard extension point. + *

          + * All auto-refresh providers must subclass this class. A + * RefreshProvider is responsible for creating + * IRefreshMonitor objects. The provider must decide if + * it is capable of monitoring the file, or folder and subtree under the path that is provided. + * + * @since 3.0 + */ +public abstract class RefreshProvider extends InternalRefreshProvider { + /** + * Creates a new refresh monitor that performs naive polling of the resource + * in the file system to detect changes. The returned monitor will immediately begin + * monitoring the specified resource root and report changes back to the workspace. + *

          + * This default monitor can be returned by subclasses when + * installMonitor is called. + *

          + * If the returned monitor is not immediately returned from the installMonitor + * method, then clients are responsible for telling the returned monitor to + * stop polling when it is no longer needed. The returned monitor can be told to + * stop working by invoking IRefreshMonitor.unmonitor(IResource). + * + * @param resource The resource to begin monitoring + * @return A refresh monitor instance + * @see #installMonitor(IResource, IRefreshResult) + */ + @Override + protected IRefreshMonitor createPollingMonitor(IResource resource) { + return super.createPollingMonitor(resource); + } + + /** + * Returns an IRefreshMonitor that will monitor a resource. If + * the resource is an IContainer the monitor will also + * monitor the subtree under the container. Returns null if + * this provider cannot create a monitor for the given resource. The + * provider may return the same monitor instance that has been provided for + * other resources. + *

          + * The monitor should send results and failures to the provided refresh + * result. + * + * @param resource the resource to monitor + * @param result the result callback for notifying of failure or of resources that need + * refreshing + * @return a monitor on the resource, or null + * if the resource cannot be monitored + * @see #createPollingMonitor(IResource) + */ + public abstract IRefreshMonitor installMonitor(IResource resource, IRefreshResult result); + + /** + * Resets the installed monitors for the given resource. This will remove all + * existing monitors that are installed on the resource, and then ask all + * refresh providers to begin monitoring the resource again. + *

          + * This method is intended to be used by refresh providers that need to change + * the refresh monitor that they previously used to monitor a resource. + * + * @param resource The resource to reset the monitors for + */ + @Override + public void resetMonitors(IResource resource) { + super.resetMonitors(resource); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html new file mode 100644 index 0000000000..4d3a2fe39a --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html @@ -0,0 +1,23 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the auto-refresh providers. + +

          Package Specification

          +

          +This package specifies the APIs in the Resources plug-in that are intended +to be implemented and used by plug-ins using the refreshProviders extension point. +This extension point is used by plug-ins to notify the workspace of changes that +have occurred externally in the file system. +

          +@since 3.0 +

          + + + diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java new file mode 100644 index 0000000000..dfdc6f7dec --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ +package org.eclipse.core.resources.team; + +import org.eclipse.core.resources.IWorkspace; + +/** + * A context that is used in conjunction with the {@link FileModificationValidator} + * to indicate that UI-based validation is desired. + *

          + * This class is not intended to be instantiated or subclassed by clients. + * + * @see FileModificationValidator + * @since 3.3 + */ +public class FileModificationValidationContext { + + /** + * Constant that can be passed to {@link IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], Object)} + * to indicate that the caller does not have access to a UI context but would still + * like to have UI-based validation if possible. + */ + public static final FileModificationValidationContext VALIDATE_PROMPT = new FileModificationValidationContext(null); + + private final Object shell; + + /** + * Create a context with the given shell. + * + * @param shell the shell + */ + FileModificationValidationContext(Object shell) { + this.shell = shell; + } + + /** + * Return the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null if there is no UI context + * available (declared as an Object to avoid any direct references on the SWT component). + * If there is no shell, the {@link FileModificationValidator} may still perform + * UI-based validation if they can obtain a Shell from another source. + * @return the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null + */ + public Object getShell() { + return shell; + } +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java new file mode 100644 index 0000000000..926988a3f8 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.resources.team; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IStatus; + +/** + * The file modification validator is a Team-related hook for pre-checking operations + * that modify the contents of files. + *

          + * This class is used only in conjunction with the + * "org.eclipse.core.resources.fileModificationValidator" + * extension point. It is intended to be implemented only + * by the Eclipse Platform Team plug-in or by repository providers + * whose validator get invoked by Team. + *

          + * @since 3.3 + */ +@SuppressWarnings("deprecation") +public abstract class FileModificationValidator implements IFileModificationValidator { + + /** + * Validates that the given files can be modified. The files must all exist + * in the workspace. The optional context object may be supplied if + * UI-based validation is required. If the context is null, the + * validator must attempt to perform the validation in a headless manner. + * The returned status is IStatus.OK if this validator + * believes the given file can be modified. Other return statuses indicate + * the reason why the individual files cannot be modified. + * + * @param files the files that are to be modified; these files must all exist in the workspace + * @param context the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null if there is no UI context (declared + * as an Object to avoid any direct references on the SWT component) + * @return a status object that is OK if things are fine, otherwise a status describing + * reasons why modifying the given files is not reasonable + * @see IWorkspace#validateEdit(IFile[], Object) + * @deprecated this method is part of the deprecated {@link IFileModificationValidator} + * interface. Clients should call {@link #validateEdit(IFile[], FileModificationValidationContext)} + * instead. + */ + @Deprecated + @Override + public final IStatus validateEdit(IFile[] files, Object context) { + FileModificationValidationContext validationContext; + if (context == null) + validationContext = null; + else if (context instanceof FileModificationValidationContext) + validationContext = (FileModificationValidationContext) context; + else + validationContext = new FileModificationValidationContext(context); + return validateEdit(files, validationContext); + } + + /** + * Validates that the given file can be saved. This method is called from + * IFile#setContents and IFile#appendContents + * before any attempt to write data to disk. The returned status is + * IStatus.OK if this validator believes the given file can be + * successfully saved. In all other cases the return value is a non-OK status. + * Note that a return value of IStatus.OK does not guarantee + * that the save will succeed. + * + * @param file the file that is to be modified; this file must exist in the workspace + * @return a status indicating whether or not it is reasonable to try writing to the given file; + * IStatus.OK indicates a save should be attempted. + * + * @see IFile#setContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + * @see IFile#appendContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public IStatus validateSave(IFile file) { + return validateEdit(new IFile[] {file}, (FileModificationValidationContext) null); + } + + /** + * Validates that the given files can be modified. The files must all exist + * in the workspace. The optional context may be supplied if + * UI-based validation is required. If the context is null, the + * validator must attempt to perform the validation in a headless manner. + * The returned status is IStatus.OK if this validator + * believes the given file can be modified. Other return statuses indicate + * the reason why the individual files cannot be modified. + * + * @param files the files that are to be modified; these files must all exist in the workspace + * @param context the context to aid in UI-based validation or null if the validation + * must be headless + * @return a status object that is OK if things are fine, otherwise a status describing + * reasons why modifying the given files is not reasonable + * @see IWorkspace#validateEdit(IFile[], Object) + */ + public abstract IStatus validateEdit(IFile[] files, FileModificationValidationContext context); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java new file mode 100644 index 0000000000..d7d2ea84d6 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java @@ -0,0 +1,383 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.team; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +/** + * Primary interface for hooking the implementation of + * IResource.move and IResource.delete. + *

          + * This interface is intended to be implemented by the team component in + * conjunction with the org.eclipse.core.resources.moveDeleteHook + * standard extension point. Individual team providers may also implement this + * interface. It is not intended to be implemented by other clients. The methods + * defined on this interface are called from within the implementations of + * IResource.move and IResource.delete. They are not + * intended to be called from anywhere else. + *

          + * + * @since 2.0 + */ +public interface IMoveDeleteHook { + + /** + * Implements IResource.delete(int,IProgressMonitor) where the + * receiver is a file. Returns true to accept responsibility + * for implementing this operation as per the API contract. + *

          + * In broad terms, a full re-implementation should delete the file in the + * local file system and then call tree.deletedFile to complete + * the updating of the workspace resource tree to reflect this fact. If + * unsuccessful in deleting the file from the local file system, it + * should instead call tree.failed to report the reason for + * the failure. In either case, it should return true to + * indicate that the operation was attempted. The FORCE update + * flag needs to be honored: unless FORCE is specified, the + * implementation must use tree.isSynchronized to determine + * whether the file is in sync before attempting to delete it. + * The KEEP_HISTORY update flag needs to be honored as well; + * use tree.addToLocalHistory to capture the contents of the + * file before deleting it from the local file system. + *

          + * An extending implementation should perform whatever pre-processing it + * needs to do and then call tree.standardDeleteFile to + * explicitly invoke the standard file deletion behavior, which deletes + * both the file from the local file system and updates the workspace + * resource tree. It should return true to indicate that the + * operation was attempted. + *

          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteFile and returning true. + *

          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param file the handle of the file to delete; the receiver of + * IResource.delete(int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this method + * attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public boolean deleteFile(IResourceTree tree, IFile file, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.delete(int,IProgressMonitor) where the + * receiver is a folder. Returns true to accept responsibility + * for implementing this operation as per the API contract. + *

          + * In broad terms, a full re-implementation should delete the directory tree + * in the local file system and then call tree.deletedFolder to + * complete the updating of the workspace resource tree to reflect this fact. + * If unsuccessful in deleting the directory or any of its descendents from + * the local file system, it should instead call tree.failed to + * report each reason for failure. In either case it should return + * true to indicate that the operation was attempted. + * The FORCE update flag needs to be honored: unless + * FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the folder + * subtree is in sync before attempting to delete it. + * The KEEP_HISTORY update flag needs to be honored as well; + * use tree.addToLocalHistory to capture the contents of any + * files being deleted. + *

          + * A partial re-implementation should perform whatever pre-processing it + * needs to do and then call tree.standardDeleteFolder to + * explicitly invoke the standard folder deletion behavior, which deletes + * both the folder and its descendents from the local file system and + * updates the workspace resource tree. It should return true + * to indicate that the operation was attempted. + *

          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteFolder and returning true. + *

          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param folder the handle of the folder to delete; the receiver of + * IResource.delete(int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public boolean deleteFolder(IResourceTree tree, IFolder folder, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.delete(int,IProgressMonitor) where the + * receiver is a project. Returns true to accept responsibility + * for implementing this operation as per the API contract. + *

          + * In broad terms, a full re-implementation should delete the project content area in + * the local file system if required (the files of a closed project should be deleted + * only if the IResource.ALWAYS_DELETE_PROJECT_CONTENTS update + * flag is specified; the files of an open project should be deleted unless the + * the IResource.NEVER_DELETE_PROJECT_CONTENTS update flag is + * specified). It should then call tree.deletedProject to complete + * the updating of the workspace resource tree to reflect this fact. If unsuccessful + * in deleting the project's files from the local file system, it should instead call + * tree.failed to report the reason for the failure. In either case, it + * should return true to indicate that the operation was attempted. + * The FORCE update flag may need to be honored if the project is open: + * unless FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the project subtree is in + * sync before attempting to delete it. + * Note that local history is not maintained when a project is deleted, + * regardless of the setting of the KEEP_HISTORY update flag. + *

          + * A partial re-implementation should perform whatever pre-processing it needs + * to do and then call tree.standardDeleteProject to explicitly + * invoke the standard project deletion behavior. It should return true + * to indicate that the operation was attempted. + *

          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteProject and returning true. + *

          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param project the handle of the project to delete; the receiver of + * IResource.delete(int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public boolean deleteProject(IResourceTree tree, IProject project, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.move(IPath,int,IProgressMonitor) where + * the receiver is a file. Returns true to accept + * responsibility for implementing this operation as per the API contract. + *

          + * On entry to this hook method, the following is guaranteed about the + * workspace resource tree: the source file exists; the destination file + * does not exist; the container of the destination file exists and is + * accessible. In broad terms, a full re-implementation should move the file + * in the local file system and then call tree.moveFile to + * complete the updating of the workspace resource tree to reflect this + * fact. If unsuccessful in moving the file in the local file system, + * it should instead call tree.failed to report the reason for + * the failure. In either case, it should return true to + * indicate that the operation was attempted. + * The FORCE update flag needs to be honored: unless + * FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the file is in sync before + * attempting to move it. + * The KEEP_HISTORY update flag needs to be honored as well; use + * tree.addToLocalHistory to capture the contents of the file + * (naturally, this must be before moving the file from the local file system). + *

          + * An extending implementation should perform whatever pre-processing it needs + * to do and then call tree.standardMoveFile to explicitly + * invoke the standard file moving behavior, which moves both the file in the + * local file system and updates the workspace resource tree. It should return + * true to indicate that the operation was attempted. + *

          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardMoveFile and returning true. + *

          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param source the handle of the file to move; the receiver of + * IResource.move(IPath,int,IProgressMonitor) + * @param destination the handle of where the file will move to; the handle + * equivalent of the first parameter to + * IResource.move(IPath,int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(org.eclipse.core.runtime.IPath,int,IProgressMonitor) + */ + public boolean moveFile(IResourceTree tree, IFile source, IFile destination, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.move(IPath,int,IProgressMonitor) where + * the receiver is a project. Returns true to accept + * responsibility for implementing this operation as per the API contract. + *

          + * On entry to this hook method, the following is guaranteed about the + * workspace resource tree: the source folder exists; the destination folder + * does not exist; the container of the destination folder exists and is + * accessible. In broad terms, a full re-implementation should move the + * directory tree in the local file system and then call + * tree.movedFolder to complete the updating of the workspace + * resource tree to reflect this fact. If unsuccessful in moving the + * directory or any of its descendents in the local file system, + * call tree.failed to report each reason for failure. + * In either case, return true to indicate that the operation + * was attempted. + * The FORCE update flag needs to be honored: unless + * FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the folder subtree is in sync + * before attempting to move it. + * The KEEP_HISTORY update flag needs to be honored as well; use + * tree.addToLocalHistory to capture the contents of any files being + * moved. + *

          + * A partial re-implementation should perform whatever pre-processing it needs + * to do and then call tree.standardMoveFolder to explicitly + * invoke the standard folder move behavior, which move both the folder + * and its descendents in the local file system and updates the workspace resource + * tree. Return true to indicate that the operation was attempted. + *

          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteFolder and returning true. + *

          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param source the handle of the folder to move; the receiver of + * IResource.move(IPath,int,IProgressMonitor) + * @param destination the handle of where the folder will move to; the + * handle equivalent of the first parameter to + * IResource.move(IPath,int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(org.eclipse.core.runtime.IPath,int,IProgressMonitor) + */ + public boolean moveFolder(IResourceTree tree, IFolder source, IFolder destination, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.move(IPath,int,IProgressMonitor) and + * IResource.move(IProjectDescription,int,IProgressMonitor) + * where the receiver is a project. Returns true to accept + * responsibility for implementing this operation as per the API contracts. + *

          + * On entry to this hook method, the source project is guaranteed to exist + * and be open in the workspace resource tree. If the given description + * contains a different name from that of the given project, then the + * project is being renamed (and its content possibly relocated). If the + * given description contains the same name as the given project, then the + * project is being relocated but not renamed. When the project is being + * renamed, the destination project is guaranteed not to exist in the + * workspace resource tree. + *

          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardMoveProject and returning true. + *

          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param source the handle of the open project to move; the receiver of + * IResource.move(IProjectDescription,int,IProgressMonitor) + * or IResource.move(IPath,int,IProgressMonitor) + * @param description the new description of the project; the first + * parameter to + * IResource.move(IProjectDescription,int,IProgressMonitor), or + * a copy of the project's description with the location changed to the + * path given in the first parameter to + * IResource.move(IPath,int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IProjectDescription,int,IProgressMonitor) + * or IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IProjectDescription,int,IProgressMonitor) + * or IResource.move(IPath,int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this method + * attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(org.eclipse.core.runtime.IPath,int,IProgressMonitor) + * @see IResource#move(IProjectDescription,int,IProgressMonitor) + */ + public boolean moveProject(IResourceTree tree, IProject source, IProjectDescription description, int updateFlags, IProgressMonitor monitor); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java new file mode 100644 index 0000000000..c3bd43da9a --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java @@ -0,0 +1,383 @@ +/******************************************************************************* + * 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.resources.team; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; + +/** + * Provides internal access to the workspace resource tree for the purposes of + * implementing the move and delete operations. Implementations of + * IMoveDeleteHook call these methods. + * + * @since 2.0 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IResourceTree { + + /** + * Constant indicating that no file timestamp was supplied. + * + * @see #movedFile(IFile, IFile) + */ + public static final long NULL_TIMESTAMP = 0L; + + /** + * Adds the current state of the given file to the local history. + * Does nothing if the file does not exist in the workspace resource tree, + * or if it exists in the workspace resource tree but not in the local file + * system. + *

          + * This method is used to capture the state of a file in the workspace + * local history before it is overwritten or deleted. + *

          + * + * @param file the file to be captured + */ + public void addToLocalHistory(IFile file); + + /** + * Returns whether the given resource and its descendents to the given depth + * are considered to be in sync with the local file system. Returns + * false if the given resource does not exist in the workspace + * resource tree, but exists in the local file system; and conversely. + * + * @param resource the resource of interest + * @param depth the depth (one of IResource.DEPTH_ZERO, + * DEPTH_ONE, or DEPTH_INFINITE) + * @return true if the resource is synchronized, and + * false in all other cases + */ + public boolean isSynchronized(IResource resource, int depth); + + /** + * Computes the timestamp for the given file in the local file system. + * Returns NULL_TIMESTAMP if the timestamp of the file in + * the local file system cannot be determined. The file need not exist in + * the workspace resource tree; however, if the file's project does not + * exist in the workspace resource tree, this method returns + * NULL_TIMESTAMP because the project's local content area + * is indeterminate. + *

          + * Note that the timestamps used for workspace resource tree file + * synchronization are not necessarily interchangeable with + * java.io.File last modification time.The ones computed by + * computeTimestamp may have a higher resolution in some + * operating environments. + *

          + * + * @param file the file of interest + * @return the local file system timestamp for the file, or + * NULL_TIMESTAMP if it could not be computed + */ + public long computeTimestamp(IFile file); + + /** + * Returns the timestamp for the given file as recorded in the workspace + * resource tree. Returns NULL_TIMESTAMP if the given file + * does not exist in the workspace resource tree, or if the timestamp is + * not known. + *

          + * Note that the timestamps used for workspace resource tree file + * synchronization are not necessarily interchangeable with + * java.io.File last modification time.The ones computed by + * computeTimestamp may have a higher resolution in some + * operating environments. + *

          + * + * @param file the file of interest + * @return the workspace resource tree timestamp for the file, or + * NULL_TIMESTAMP if the file does not exist in the + * workspace resource tree, or if the timestamp is not known + */ + public long getTimestamp(IFile file); + + /** + * Updates the timestamp for the given file in the workspace resource tree. + * The file is the local file system is not affected. Does nothing if the + * given file does not exist in the workspace resource tree. + *

          + * The given timestamp should be that of the corresponding file in the local + * file system (as computed by computeTimestamp). A discrepancy + * between the timestamp of the file in the local file system and the + * timestamp recorded in the workspace resource tree means that the file is + * out of sync (isSynchronized returns false). + *

          + *

          + * This operation should be used after movedFile/Folder/Project + * to correct the workspace resource tree record when file timestamps change + * in the course of a move operation. + *

          + *

          + * Note that the timestamps used for workspace resource tree file + * synchronization are not necessarily interchangeable with + * java.io.File last modification time.The ones computed by + * computeTimestamp may have a higher resolution in some + * operating environments. + *

          + * + * @param file the file of interest + * @param timestamp the local file system timestamp for the file, or + * NULL_TIMESTAMP if unknown + * @see #computeTimestamp(IFile) + */ + public void updateMovedFileTimestamp(IFile file, long timestamp); + + /** + * Declares that the operation has failed for the specified reason. + * This method may be called multiple times to report multiple + * failures. All reasons will be accumulated and taken into consideration + * when deciding the outcome of the hooked operation as a whole. + * + * @param reason the reason the operation (or sub-operation) failed + */ + public void failed(IStatus reason); + + /** + * Declares that the given file has been successfully deleted from the + * local file system, and requests that the corresponding deletion should + * now be made to the workspace resource tree. No action is taken if the + * given file does not exist in the workspace resource tree. + *

          + * This method clears out any markers, session properties, and persistent + * properties associated with the given file. + *

          + * + * @param file the file that was just deleted from the local file system + */ + public void deletedFile(IFile file); + + /** + * Declares that the given folder and all its descendents have been + * successfully deleted from the local file system, and requests that the + * corresponding deletion should now be made to the workspace resource tree. + * No action is taken if the given folder does not exist in the workspace + * resource tree. + *

          + * This method clears out any markers, session properties, and persistent + * properties associated with the given folder or its descendents. + *

          + * + * @param folder the folder that was just deleted from the local file system + */ + public void deletedFolder(IFolder folder); + + /** + * Declares that the given project's content area in the local file system + * has been successfully dealt with in an appropriate manner, and requests + * that the corresponding deletion should now be made to the workspace + * resource tree. No action is taken if the given project does not exist in + * the workspace resource tree. + *

          + * This method clears out everything associated with this project and any of + * its descendent resources, including: markers; session properties; + * persistent properties; local history; and project-specific plug-ins + * working data areas. The project's content area is not affected. + *

          + * + * @param project the project being deleted + */ + public void deletedProject(IProject project); + + /** + * Declares that the given source file has been successfully moved to the + * given destination in the local file system, and requests that the + * corresponding changes should now be made to the workspace resource tree. + * No action is taken if the given source file does not exist in the + * workspace resource tree. + *

          + * The destination file must not already exist in the workspace resource + * tree. + *

          + * This operation carries over the file timestamp unchanged. Use + * updateMovedFileTimestamp to update the timestamp + * of the file if its timestamp changed as a direct consequence of the move. + *

          + * + * @param source the handle of the source file that was moved + * @param destination the handle of where the file moved to + * @see #computeTimestamp(IFile) + */ + public void movedFile(IFile source, IFile destination); + + /** + * Declares that the given source folder and its descendents have been + * successfully moved to the given destination in the local file system, + * and requests that the corresponding changes should now be made to the + * workspace resource tree for the folder and all its descendents. No action + * is taken if the given source folder does not exist in the workspace + * resource tree. + *

          + * This operation carries over file timestamps unchanged. Use + * updateMovedFileTimestamp to update the timestamp of files + * whose timestamps changed as a direct consequence of the move. + *

          + * The destination folder must not already exist in the workspace resource + * tree. + *

          + * + * @param source the handle of the source folder that was moved + * @param destination the handle of where the folder moved to + */ + public void movedFolderSubtree(IFolder source, IFolder destination); + + /** + * Declares that the given source project and its files and folders have + * been successfully relocated in the local file system if required, and + * requests that the rename and/or relocation should now be made to the + * workspace resource tree for the project and all its descendents. No + * action is taken if the given project does not exist in the workspace + * resource tree. + *

          + * This operation carries over file timestamps unchanged. Use + * updateMovedFileTimestamp to update the timestamp of files whose + * timestamps changed as a direct consequence of the move. + *

          + * If the project is being renamed, the destination project must not + * already exist in the workspace resource tree. + *

          + * Local history is not preserved if the project is renamed. It is preserved + * when the project's content area is relocated without renaming the + * project. + *

          + * + * @param source the handle of the source project that was moved + * @param description the new project description + * @return true if the move succeeded, and false + * otherwise + */ + public boolean movedProjectSubtree(IProject source, IProjectDescription description); + + /** + * Deletes the given file in the standard manner from both the local file + * system and from the workspace resource tree. + *

          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of file.delete(updateFlags, monitor) because all + * regular API operations that modify resources are off limits. + *

          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

          + * + * @param file the file to delete + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + */ + public void standardDeleteFile(IFile file, int updateFlags, IProgressMonitor monitor); + + /** + * Deletes the given folder and its descendents in the standard manner from + * both the local file system and from the workspace resource tree. + *

          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of folder.delete(updateFlags, monitor) because all + * regular API operations that modify resources are off limits. + *

          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

          + * + * @param folder the folder to delete + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + */ + public void standardDeleteFolder(IFolder folder, int updateFlags, IProgressMonitor monitor); + + /** + * Deletes the given project and its descendents in the standard manner from + * both the local file system and from the workspace resource tree. + *

          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of project.delete(updateFlags, monitor) because all + * regular API operations that modify resources are off limits. + *

          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

          + * + * @param project the project to delete + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + */ + public void standardDeleteProject(IProject project, int updateFlags, IProgressMonitor monitor); + + /** + * Moves the given file in the standard manner from both the local file + * system and from the workspace resource tree. + *

          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of source.move(destination.getProjectRelativePath(), + * updateFlags, monitor) because all regular API operations that + * modify resources are off limits. + *

          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

          + * + * @param source the handle of the source file to move + * @param destination the handle of where the file will move to + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + */ + public void standardMoveFile(IFile source, IFile destination, int updateFlags, IProgressMonitor monitor); + + /** + * Moves the given folder and its descendents in the standard manner from + * both the local file system and from the workspace resource tree. + *

          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of source.move(destination.getProjectRelativePath(), + * updateFlags, monitor) because all regular API operations that + * modify resources are off limits. + *

          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

          + * + * @param source the handle of the source folder to move + * @param destination the handle of where the folder will move to + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + */ + public void standardMoveFolder(IFolder source, IFolder destination, int updateFlags, IProgressMonitor monitor); + + /** + * Renames and/or relocates the given project in the standard manner. + *

          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of source.move(description, updateFlags, monitor) + * because all regular API operations that modify resources are off limits. + *

          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

          + * + * @param source the handle of the source folder to move + * @param description the new project description + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + */ + public void standardMoveProject(IProject source, IProjectDescription description, int updateFlags, IProgressMonitor monitor); +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java new file mode 100644 index 0000000000..aaf53bee97 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java @@ -0,0 +1,273 @@ +/******************************************************************************* + * 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 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +package org.eclipse.core.resources.team; + +import java.util.HashSet; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; + +/** + * Default implementation of IResourceRuleFactory. The teamHook extension + * may subclass to provide more specialized scheduling rules for workspace operations that + * they participate in. + * + * @see IResourceRuleFactory + * @since 3.0 + */ +public class ResourceRuleFactory implements IResourceRuleFactory { + private final IWorkspace workspace = ResourcesPlugin.getWorkspace(); + + /** + * Creates a new default resource rule factory. This constructor must only + * be called by subclasses. + */ + protected ResourceRuleFactory() { + super(); + } + + /** + * Default implementation of IResourceRuleFactory#buildRule. + * This default implementation always returns the workspace root. + *

          + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.resources.IResourceRuleFactory#buildRule() + */ + @Override + public final ISchedulingRule buildRule() { + return workspace.getRoot(); + } + + /** + * Default implementation of IResourceRuleFactory#charsetRule. + * This default implementation always returns the project of the resource + * whose charset setting is being changed, or null if the + * resource is the workspace root. + *

          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + *

          + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#charsetRule(IResource) + * @since 3.1 + */ + @Override + public ISchedulingRule charsetRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return null; + return resource.getProject(); + } + + /** + * Default implementation of IResourceRuleFactory#derivedRule. + * This default implementation always returns null. + *

          + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#derivedRule(IResource) + * @since 3.6 + */ + @Override + public final ISchedulingRule derivedRule(IResource resource) { + return null; + } + + /** + * Default implementation of IResourceRuleFactory#copyRule. + * This default implementation always returns the parent of the destination + * resource. + *

          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#copyRule(IResource, IResource) + */ + @Override + public ISchedulingRule copyRule(IResource source, IResource destination) { + //source is not modified, destination is created + return parent(destination); + } + + /** + * Default implementation of IResourceRuleFactory#createRule. + * This default implementation always returns the parent of the resource + * being created. + *

          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#createRule(IResource) + */ + @Override + public ISchedulingRule createRule(IResource resource) { + return parent(resource); + } + + /** + * Default implementation of IResourceRuleFactory#deleteRule. + * This default implementation always returns the parent of the resource + * being deleted. + *

          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#deleteRule(IResource) + */ + @Override + public ISchedulingRule deleteRule(IResource resource) { + return parent(resource); + } + + private boolean isReadOnly(IResource resource) { + ResourceAttributes attributes = resource.getResourceAttributes(); + return attributes == null ? false : attributes.isReadOnly(); + } + + /** + * Default implementation of IResourceRuleFactory#markerRule. + * This default implementation always returns null. + *

          + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.resources.IResourceRuleFactory#markerRule(IResource) + */ + @Override + public final ISchedulingRule markerRule(IResource resource) { + return null; + } + + /** + * Default implementation of IResourceRuleFactory#syncInfoRule. + * This default implementation always returns null. + *

          + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.resources.IResourceRuleFactory#syncInfoRule(IResource) + */ + @Override + public final ISchedulingRule syncInfoRule(IResource resource) { + return null; + } + + /** + * Default implementation of IResourceRuleFactory#modifyRule. + * This default implementation returns the resource being modified, or the + * parent resource if modifying a project description file. + * Note that this must encompass any rule required by the validateSave hook. + *

          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#modifyRule(IResource) + * @see FileModificationValidator#validateSave(IFile) + * @see IProjectDescription#DESCRIPTION_FILE_NAME + */ + @Override + public ISchedulingRule modifyRule(IResource resource) { + IPath path = resource.getFullPath(); + //modifying the project description may cause linked resources to be created or deleted + if (path.segmentCount() == 2 && path.segment(1).equals(IProjectDescription.DESCRIPTION_FILE_NAME)) + return parent(resource); + return resource; + } + + /** + * Default implementation of IResourceRuleFactory#moveRule. + * This default implementation returns a rule that combines the parent + * of the source resource and the parent of the destination resource. + *

          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#moveRule(IResource, IResource) + */ + @Override + public ISchedulingRule moveRule(IResource source, IResource destination) { + //move needs the parent of both source and destination + return MultiRule.combine(parent(source), parent(destination)); + } + + /** + * Convenience method to return the parent of the given resource, + * or the resource itself for projects and the workspace root. + * @param resource the resource to compute the parent of + * @return the parent resource for folders and files, and the + * resource itself for projects and the workspace root. + */ + protected final ISchedulingRule parent(IResource resource) { + switch (resource.getType()) { + case IResource.ROOT : + case IResource.PROJECT : + return resource; + default : + return resource.getParent(); + } + } + + /** + * Default implementation of IResourceRuleFactory#refreshRule. + * This default implementation always returns the parent of the resource + * being refreshed. + *

          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#refreshRule(IResource) + */ + @Override + public ISchedulingRule refreshRule(IResource resource) { + return parent(resource); + } + + /** + * Default implementation of IResourceRuleFactory#validateEditRule. + * This default implementation returns a rule that combines the parents of + * all read-only resources, or null if there are no read-only + * resources. + *

          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#validateEditRule(IResource[]) + */ + @Override + public ISchedulingRule validateEditRule(IResource[] resources) { + if (resources.length == 0) + return null; + //optimize rule for single file + if (resources.length == 1) + return isReadOnly(resources[0]) ? parent(resources[0]) : null; + //need a lock on the parents of all read-only files + HashSet rules = new HashSet(); + for (int i = 0; i < resources.length; i++) + if (isReadOnly(resources[i])) + rules.add(parent(resources[i])); + if (rules.isEmpty()) + return null; + if (rules.size() == 1) + return rules.iterator().next(); + ISchedulingRule[] ruleArray = rules.toArray(new ISchedulingRule[rules.size()]); + return new MultiRule(ruleArray); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java new file mode 100644 index 0000000000..6dac1946a5 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java @@ -0,0 +1,231 @@ +/******************************************************************************* + * 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.team; + +import java.net.URI; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.resources.InternalTeamHook; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * A general hook class for operations that team providers may be + * interested in participating in. Implementors of the hook should provide + * a concrete subclass, and override any methods they are interested in. + *

          + * This class is intended to be subclassed by the team component in + * conjunction with the org.eclipse.core.resources.teamHook + * standard extension point. Individual team providers may also subclass this + * class. It is not intended to be subclassed by other clients. The methods + * defined on this class are called from within the implementations of + * workspace API methods and must not be invoked directly by clients. + *

          + * + * @since 2.1 + */ +public abstract class TeamHook extends InternalTeamHook { + /** + * The default resource scheduling rule factory. This factory can be used for projects + * that the team hook methods do not participate in. + * + * @see #getRuleFactory(IProject) + * @see #setRuleFactory(IProject, IResourceRuleFactory) + * @since 3.0 + */ + protected final IResourceRuleFactory defaultFactory = new ResourceRuleFactory(); + + /** + * Creates a new team hook. Default constructor for use by subclasses and the + * resources plug-in only. + */ + protected TeamHook() { + super(); + } + + /** + * Returns the resource scheduling rule factory that should be used when workspace + * operations are invoked on resources in that project. The workspace will ask the + * team hook this question only once per project, per session. The workspace will + * assume the returned result is valid for the rest of that session, unless the rule + * is changed by calling setRuleFactory. + *

          + * This method must not return null. If no special rules are required + * by the team hook for the given project, the value of the defaultFactory + * field should be returned. + *

          + * This default implementation always returns the value of the defaultFactory + * field. Subclasses may override and provide a subclass of ResourceRuleFactory. + * + * @param project the project to return scheduling rules for + * @return the resource scheduling rules for a project + * @see #setRuleFactory(IProject, IResourceRuleFactory) + * @see ResourceRuleFactory + * @since 3.0 + */ + public IResourceRuleFactory getRuleFactory(IProject project) { + return defaultFactory; + } + + /** + * Sets the resource scheduling rule factory to use for resource modifications + * in the given project. This method only needs to be called if the factory has changed + * since the initial call to getRuleFactory for the given project + *

          + * The supplied factory must not be null. If no special rules are required + * by the team hook for the given project, the value of the defaultFactory + * field should be used. + *

          + * Note that the new rule factory will only take effect for resource changing + * operations that begin after this method completes. Care should be taken to + * avoid calling this method during the invocation of any resource changing + * operation (in any thread). The best time to change rule factories is during resource + * change notification when the workspace is locked for modification. + * + * @param project the project to change the resource rule factory for + * @param factory the new resource rule factory + * @see #getRuleFactory(IProject) + * @see IResourceRuleFactory + * @since 3.0 + */ + @Override + protected final void setRuleFactory(IProject project, IResourceRuleFactory factory) { + super.setRuleFactory(project, factory); + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of IFile.createLink. + *

          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

          + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

          + * + * @param file the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system path where the file should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + */ + public IStatus validateCreateLink(IFile file, int updateFlags, IPath location) { + return Status.OK_STATUS; + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of {@link IFile#createLink(URI, int, IProgressMonitor) } + *

          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

          + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

          + * + * @param file the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system URI where the file should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + * @since 3.2 + */ + public IStatus validateCreateLink(IFile file, int updateFlags, URI location) { + //forward to old method to ensure old hooks get a chance to validate in the local case + if (EFS.SCHEME_FILE.equals(location.getScheme())) + return validateCreateLink(file, updateFlags, URIUtil.toPath(location)); + return Status.OK_STATUS; + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of IFolder.createLink. + *

          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

          + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

          + * + * @param folder the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system path where the folder should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + */ + public IStatus validateCreateLink(IFolder folder, int updateFlags, IPath location) { + return Status.OK_STATUS; + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of {@link IFolder#createLink(URI, int, IProgressMonitor)} + *

          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

          + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

          + * + * @param folder the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system path where the folder should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + * @since 3.2 + */ + public IStatus validateCreateLink(IFolder folder, int updateFlags, URI location) { + //forward to old method to ensure old hooks get a chance to validate in the local case + if (EFS.SCHEME_FILE.equals(location.getScheme())) + return validateCreateLink(folder, updateFlags, URIUtil.toPath(location)); + return Status.OK_STATUS; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html new file mode 100644 index 0000000000..5069b75ab0 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html @@ -0,0 +1,19 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the Team component. + +

          Package Specification

          +This package specifies the APIs in the Resources plug-in which are intended +to be implemented by the Team component. This provides hooks into the +internal workspace tree mechanism as well as lets Team providers receive pre-notification +for resource API calls such as move and delete. + + + diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java new file mode 100644 index 0000000000..341c0a839b --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.resources.variableresolvers; + +import org.eclipse.core.resources.IResource; + +/** + * An interface that variable providers should implement in order + * to extends the default path variable list used to resolve relative + * locations of linked resources. + * @since 3.6 + */ +public abstract class PathVariableResolver { + + /** + * This method can return a list of possible variables resolved by + * this resolver. + *

          + * This default implementation always returns null. Subclasses + * should override to provide custom extensions. + *

          + * + * @param variable + * The current variable name. + * @param resource + * The resource that the variable is being resolved for. + * @return the list of supported variables + */ + public String[] getVariableNames(String variable, IResource resource) { + return null; + } + + /** + * Returns a variable value + * + * @param variable + * The current variable name. + * @param resource + * The resource that the variable is being resolved for. + * @return the variable value. + */ + public abstract String getValue(String variable, IResource resource); +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html new file mode 100644 index 0000000000..6a477adf0e --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html @@ -0,0 +1,21 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the path variable providers. + +

          Package Specification

          +

          +This package specifies the APIs in the Resources plug-in that are intended +to be implemented and used by plug-ins using the variableResolvers extension point. +

          +@since 3.6 +

          + + + diff --git a/third_party/patches/mars/pom.xml b/third_party/patches/mars/pom.xml new file mode 100644 index 0000000000..f4d6419657 --- /dev/null +++ b/third_party/patches/mars/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + com.google.cloud.tools.eclipse + trunk + 0.1.0-SNAPSHOT + ../.. + + eclipse-patches-mars + 0.1.0-SNAPSHOT + pom + + Patches bundles for Eclipse Mars + + + org.eclipse.core.resources + + diff --git a/third_party/patches/neon/org.eclipse.core.resources/.classpath b/third_party/patches/neon/org.eclipse.core.resources/.classpath new file mode 100644 index 0000000000..1582404385 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/third_party/patches/neon/org.eclipse.core.resources/.project b/third_party/patches/neon/org.eclipse.core.resources/.project new file mode 100644 index 0000000000..5b64045589 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/.project @@ -0,0 +1,17 @@ + + + org.eclipse.core.resources + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/third_party/patches/neon/org.eclipse.core.resources/META-INF/MANIFEST.MF b/third_party/patches/neon/org.eclipse.core.resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..851f42d282 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/META-INF/MANIFEST.MF @@ -0,0 +1,32 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true +Bundle-Version: 3.11.1.v20161107-2032 +Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin +Bundle-Vendor: %providerName +Bundle-Localization: plugin +Export-Package: org.eclipse.core.internal.dtree;x-internal:=true, + org.eclipse.core.internal.events;x-internal:=true, + org.eclipse.core.internal.localstore;x-internal:=true, + org.eclipse.core.internal.properties;x-internal:=true, + org.eclipse.core.internal.propertytester;x-internal:=true, + org.eclipse.core.internal.refresh;x-internal:=true, + org.eclipse.core.internal.resources;x-internal:=true, + org.eclipse.core.internal.resources.mapping;x-internal:=true, + org.eclipse.core.internal.resources.projectvariables;x-internal:=true, + org.eclipse.core.internal.resources.refresh.win32;x-internal:=true, + org.eclipse.core.internal.utils;x-internal:=true, + org.eclipse.core.internal.watson;x-internal:=true, + org.eclipse.core.resources, + org.eclipse.core.resources.filtermatchers, + org.eclipse.core.resources.mapping, + org.eclipse.core.resources.refresh, + org.eclipse.core.resources.team, + org.eclipse.core.resources.variableresolvers +Require-Bundle: org.eclipse.ant.core;bundle-version="[3.1.0,4.0.0)";resolution:=optional, + org.eclipse.core.expressions;bundle-version="[3.2.0,4.0.0)", + org.eclipse.core.filesystem;bundle-version="[1.3.0,2.0.0)", + org.eclipse.core.runtime;bundle-version="[3.12.0,4.0.0)" +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 diff --git a/third_party/patches/neon/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties b/third_party/patches/neon/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties new file mode 100644 index 0000000000..0e23ed632d --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties @@ -0,0 +1,4 @@ +#Source Bundle Localization +#Tue Nov 15 13:52:30 EST 2016 +bundleVendor=Eclipse.org +bundleName=Core Resource Management Source diff --git a/third_party/patches/neon/org.eclipse.core.resources/about.html b/third_party/patches/neon/org.eclipse.core.resources/about.html new file mode 100644 index 0000000000..460233046e --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/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/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/META-INF/eclipse.inf b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/ConvertPath.java b/third_party/patches/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/ConvertPath.java new file mode 100644 index 0000000000..8b55fea240 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/IncrementalBuild.java b/third_party/patches/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/IncrementalBuild.java new file mode 100644 index 0000000000..f30025867d --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/Policy.java b/third_party/patches/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/Policy.java new file mode 100644 index 0000000000..de2c8da95f --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/RefreshLocalTask.java b/third_party/patches/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/RefreshLocalTask.java new file mode 100644 index 0000000000..19f2d7b963 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/messages.properties b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/build.properties b/third_party/patches/neon/org.eclipse.core.resources/build.properties new file mode 100644 index 0000000000..f646e2c10a --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/natives/make.bat b/third_party/patches/neon/org.eclipse.core.resources/natives/make.bat new file mode 100644 index 0000000000..43da22c8b7 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/natives/readme.txt b/third_party/patches/neon/org.eclipse.core.resources/natives/readme.txt new file mode 100644 index 0000000000..e27ecd6c00 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/natives/ref.c b/third_party/patches/neon/org.eclipse.core.resources/natives/ref.c new file mode 100644 index 0000000000..5c9907de1b --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/natives/ref.h b/third_party/patches/neon/org.eclipse.core.resources/natives/ref.h new file mode 100644 index 0000000000..15c5ba9ffb --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/plugin.properties b/third_party/patches/neon/org.eclipse.core.resources/plugin.properties new file mode 100644 index 0000000000..1c45ea381d --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/plugin.xml b/third_party/patches/neon/org.eclipse.core.resources/plugin.xml new file mode 100644 index 0000000000..7f8663ce31 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/plugin.xml @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/patches/neon/org.eclipse.core.resources/schema/builders.exsd b/third_party/patches/neon/org.eclipse.core.resources/schema/builders.exsd new file mode 100644 index 0000000000..8db174734f --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/schema/fileModificationValidator.exsd b/third_party/patches/neon/org.eclipse.core.resources/schema/fileModificationValidator.exsd new file mode 100644 index 0000000000..2336209991 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/schema/filterMatchers.exsd b/third_party/patches/neon/org.eclipse.core.resources/schema/filterMatchers.exsd new file mode 100644 index 0000000000..5bcd92a610 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/schema/markers.exsd b/third_party/patches/neon/org.eclipse.core.resources/schema/markers.exsd new file mode 100644 index 0000000000..a263442c10 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/schema/modelProviders.exsd b/third_party/patches/neon/org.eclipse.core.resources/schema/modelProviders.exsd new file mode 100644 index 0000000000..aa9b999c97 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/schema/moveDeleteHook.exsd b/third_party/patches/neon/org.eclipse.core.resources/schema/moveDeleteHook.exsd new file mode 100644 index 0000000000..46cf633f16 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/schema/natures.exsd b/third_party/patches/neon/org.eclipse.core.resources/schema/natures.exsd new file mode 100644 index 0000000000..42cf2493d8 --- /dev/null +++ b/third_party/patches/neon/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 &quot;false&quot;, 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/neon/org.eclipse.core.resources/schema/refreshProviders.exsd b/third_party/patches/neon/org.eclipse.core.resources/schema/refreshProviders.exsd new file mode 100644 index 0000000000..b34d044b74 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/schema/teamHook.exsd b/third_party/patches/neon/org.eclipse.core.resources/schema/teamHook.exsd new file mode 100644 index 0000000000..60dc9762c5 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/schema/variableResolvers.exsd b/third_party/patches/neon/org.eclipse.core.resources/schema/variableResolvers.exsd new file mode 100644 index 0000000000..b32631b164 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTree.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTree.java new file mode 100644 index 0000000000..f5ef7084c7 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java new file mode 100644 index 0000000000..ad395a0f50 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java new file mode 100644 index 0000000000..3acee2c63c --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java new file mode 100644 index 0000000000..d8f7802e08 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeLookup.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java new file mode 100644 index 0000000000..9a7917b6c0 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java @@ -0,0 +1,366 @@ +/******************************************************************************* + * 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); + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java new file mode 100644 index 0000000000..502a5c716f --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java new file mode 100644 index 0000000000..7f08ebf5e2 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java new file mode 100644 index 0000000000..9dda6ed19b --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java new file mode 100644 index 0000000000..35fb66d206 --- /dev/null +++ b/third_party/patches/neon/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)); + } + + /** + * @see org.eclipse.core.internal.utils.IStringPoolParticipant#shareStrings(StringPool) + */ + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java new file mode 100644 index 0000000000..f04ce018ab --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java new file mode 100644 index 0000000000..d5012bd871 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java new file mode 100644 index 0000000000..055f9318ff --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NodeComparison.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java new file mode 100644 index 0000000000..9d21fbf8e8 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/TestHelper.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java new file mode 100644 index 0000000000..9acb4dac60 --- /dev/null +++ b/third_party/patches/neon/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 volatile 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java new file mode 100644 index 0000000000..6f2f396ae2 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java @@ -0,0 +1,291 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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())); + } + } + } + + @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); + } + + @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; + } + + @Override + public String getBuilderName() { + return getName(); + } + + @Override + public int hashCode() { + // hash on name and trigger + return 37 * getName().hashCode() + (ALL_TRIGGERS & triggers); + } + + @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; + } + + @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; + } + + @Override + public void setBuilderName(String value) { + //don't allow builder name to be null + setName(value == null ? "" : value); //$NON-NLS-1$ + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java new file mode 100644 index 0000000000..ddc60ad9ea --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * 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(); + } + + @Override + public IBuildConfiguration[] getAllReferencedBuildConfigs() { + int position = findBuildConfigurationIndex(); + IBuildConfiguration[] builtBefore = new IBuildConfiguration[position]; + System.arraycopy(buildOrder, 0, builtBefore, 0, builtBefore.length); + return builtBefore; + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java new file mode 100644 index 0000000000..8ba95c81c5 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java @@ -0,0 +1,1158 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java new file mode 100644 index 0000000000..3622dfcef2 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ILifecycleListener.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java new file mode 100644 index 0000000000..fa977efbf9 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/LifecycleEvent.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NodeIDMap.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java new file mode 100644 index 0000000000..af798114da --- /dev/null +++ b/third_party/patches/neon/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 ICoreRunnable noop = new ICoreRunnable() { + @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 volatile 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java new file mode 100644 index 0000000000..4fdf5bf817 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java new file mode 100644 index 0000000000..6632b54ac2 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java new file mode 100644 index 0000000000..a3b5e4e93a --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java new file mode 100644 index 0000000000..3fe689e1d7 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java new file mode 100644 index 0000000000..8a21d6f2af --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java @@ -0,0 +1,552 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Mickael Istria (Red Hat Inc.) - Bug 488938 + *******************************************************************************/ +package org.eclipse.core.internal.events; + +import java.util.Map; +import java.util.Map.Entry; +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 (Entry entry : markerDeltas.entrySet()) { + IPath key = entry.getKey(); + if (getResource().getFullPath().equals(key)) { + MarkerSet set = entry.getValue(); + IMarkerSetElement[] deltas = set.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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java new file mode 100644 index 0000000000..58b1c2d244 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaInfo.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java new file mode 100644 index 0000000000..22562d59a2 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java new file mode 100644 index 0000000000..06de2c7edf --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java @@ -0,0 +1,138 @@ +/******************************************************************************* + * 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; + } + + /** + * @see UniversalUniqueIdentifier#appendByteString(StringBuffer, byte) + */ + @SuppressWarnings("javadoc") + 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); + } + + /** + * 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java new file mode 100644 index 0000000000..82bd8bb59b --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java @@ -0,0 +1,396 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java new file mode 100644 index 0000000000..340464cba7 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java new file mode 100644 index 0000000000..967340d7ce --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java new file mode 100644 index 0000000000..8e5f36d315 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java new file mode 100644 index 0000000000..13d9bb8e7f --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * 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); + // For a linked resource itself we have to use its root, but for its children we prefer + // to use the canonical root since it provides for faster file system access. + // See http://bugs.eclipse.org/507084 + final URI uri = resource.getPathVariableManager().resolveURI(resource.isLinked() ? root : 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); + } + IFileStore 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java new file mode 100644 index 0000000000..a7011f68d2 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java @@ -0,0 +1,1235 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * 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 + * - [462440] IFile#getContents methods should specify the status codes for its exceptions + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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. + *

          + *

          + * Returns a folder whose path has a minimal number of segments. + * I.e. a folder in a nested project is preferred over a folder in an enclosing project. + *

          + */ + public IContainer containerForLocation(IPath location) { + return (IContainer) resourceForLocation(location, false); + } + + /** + * Returns a 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. + *

          + * Returns a resource whose path has a minimal number of segments. + * I.e. a resource in a nested project is preferred over a resource in an enclosing project. + *

          + */ + private IResource resourceForLocation(IPath location, boolean files) { + if (workspace.getRoot().getLocation().equals(location)) { + if (!files) + return resourceFor(Path.ROOT, false); + return null; + } + int resultProjectPathSegments = 0; + IResource result = 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(); + if (segmentsToRemove > resultProjectPathSegments) { + IPath path = project.getFullPath().append(location.removeFirstSegments(segmentsToRemove)); + IResource resource = resourceFor(path, files); + if (resource != null && !((Resource) resource).isFiltered()) { + resultProjectPathSegments = segmentsToRemove; + result = resource; + } + } + } + } + return result; + } + + 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. + *

          + *

          + * Returns a file whose path has a minimal number of segments. + * I.e. a file in a nested project is preferred over a file in an enclosing project. + *

          + */ + 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); + String message = NLS.bind(Messages.localstore_fileNotFound, store.toString()); + throw new ResourceException(IResourceStatus.RESOURCE_NOT_FOUND, 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 { + String title = NLS.bind(Messages.localstore_refreshing, target.getFullPath()); + SubMonitor subMonitor = SubMonitor.convert(monitor, title, 100); + IFileStore fileStore = ((Resource) target).getStore(); + // Try to get all info in one shot, if the file system supports it. + IFileTree fileTree = fileStore.getFileSystem().fetchFileTree(fileStore, subMonitor.newChild(2)); + UnifiedTree tree = fileTree == null ? new UnifiedTree(target) : new UnifiedTree(target, fileTree); + SubMonitor refreshMonitor = subMonitor.newChild(98); + RefreshLocalVisitor visitor = updateAliases ? new RefreshLocalAliasVisitor(refreshMonitor) : new RefreshLocalVisitor(refreshMonitor); + tree.accept(visitor, depth); + IStatus result = visitor.getErrorStatus(); + if (!result.isOK()) + throw new ResourceException(result); + return visitor.resourcesChanged(); + } + + /** + * 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 { + IProject[] projects = target.getProjects(IContainer.INCLUDE_HIDDEN); + String title = Messages.localstore_refreshingRoot; + SubMonitor subMonitor = SubMonitor.convert(monitor, title, projects.length); + // 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, subMonitor.newChild(1)); + } + return changed; + } + + /** + * 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); + + // On Windows an attempt to open an output stream on a hidden file results in FileNotFoundException. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=194216 + boolean restoreHiddenAttribute = false; + if (fileInfo.exists() && fileInfo.getAttribute(EFS.ATTRIBUTE_HIDDEN) && Platform.getOS().equals(Platform.OS_WIN32)) { + fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, false); + store.putInfo(fileInfo, EFS.SET_ATTRIBUTES, Policy.monitorFor(null)); + restoreHiddenAttribute = true; + } + int options = append ? EFS.APPEND : EFS.NONE; + OutputStream out = store.openOutputStream(options, Policy.subMonitorFor(monitor, 0)); + if (restoreHiddenAttribute) { + fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, true); + store.putInfo(fileInfo, EFS.SET_ATTRIBUTES, Policy.monitorFor(null)); + } + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java new file mode 100644 index 0000000000..46947329c7 --- /dev/null +++ b/third_party/patches/neon/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. + *

          + * Version 2 (3.1 M5): + *

          +	 * FILE ::= VERSION_ID ENTRY+
          +	 * ENTRY ::= PATH STATE_COUNT STATE+
          +	 * PATH ::= string (does not include project name)
          +	 * STATE_COUNT ::= int
          +	 * STATE ::= UUID LAST_MODIFIED
          +	 * UUID	 ::= byte[16]
          +	 * LAST_MODIFIED ::= byte[8]
          +	 * 
          + *

          + *

          + * Version 1 (3.1 M4): + *

          +	 * FILE ::= VERSION_ID ENTRY+
          +	 * ENTRY ::= PATH STATE_COUNT STATE+
          +	 * PATH ::= string
          +	 * STATE_COUNT ::= int
          +	 * STATE ::= UUID LAST_MODIFIED
          +	 * UUID	 ::= byte[16]
          +	 * LAST_MODIFIED ::= byte[8]
          +	 * 
          + *

          + */ + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java new file mode 100644 index 0000000000..96f49382b0 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java @@ -0,0 +1,383 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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<>(); + } + } + + @Override + public void closeHistoryStore(IResource resource) { + try { + tree.getCurrent().save(); + tree.getCurrent().flush(); + } catch (CoreException e) { + log(e); + } + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java new file mode 100644 index 0000000000..68e89b532a --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/ILocalStoreConstants.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IUnifiedTreeVisitor.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java new file mode 100644 index 0000000000..6c59391511 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java new file mode 100644 index 0000000000..3312efaf23 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java new file mode 100644 index 0000000000..ff482f2d5f --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java new file mode 100644 index 0000000000..7beab2c064 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.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 + * Sergey Prigogin (Google) - [482064] Incorrect SubMonitor usage in RefreshLocalVisitor.visit + *******************************************************************************/ +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; + + // Progress monitor will initially move by 1. / TOTAL_WORK per resource but will gradually slow down + // as more resources are discovered. + public static final int TOTAL_WORK = 1000; + + protected MultiStatus errors; + protected SubMonitor monitor; + protected boolean resourceChanged; + protected Workspace workspace; + + public RefreshLocalVisitor(IProgressMonitor monitor) { + this.monitor = SubMonitor.convert(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 { + // The monitor will asymptotically approach 100% as the number of processed resources increases. + monitor.setWorkRemaining(TOTAL_WORK).worked(1); + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java new file mode 100644 index 0000000000..f9fcb9d8e4 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyOutputStream.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java new file mode 100644 index 0000000000..8ed7fa5a70 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java @@ -0,0 +1,575 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + * Sergey Prigogin (Google) - 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 = {}; + + /** Singleton to indicate no local children */ + private static final IResource[] NO_RESOURCES = {}; + + /** + * True if the level of the children of the current node are valid according + * to the requested refresh depth, false otherwise + */ + protected boolean childLevelValid; + + /** an IFileTree which can be used to build a unified tree*/ + protected IFileTree fileTree; + + /** 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.toPath().toRealPath().toString() + java.io.File.separatorChar; + String childPath = childFile.toPath().toRealPath().toString() + java.io.File.separatorChar; + //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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java new file mode 100644 index 0000000000..a296c2fafc --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java new file mode 100644 index 0000000000..e27e59eba5 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java new file mode 100644 index 0000000000..762e74e121 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java @@ -0,0 +1,350 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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. + *

          + * Version 1: + *

          +	 * FILE ::= VERSION_ID ENTRY+
          +	 * ENTRY ::= PATH PROPERTY_COUNT PROPERTY+
          +	 * PATH ::= string (does not contain project name)
          +	 * PROPERTY_COUNT ::= int
          +	 * PROPERTY ::= QUALIFIER LOCAL_NAME VALUE
          +	 * QUALIFIER ::= INDEX | QNAME
          +	 * INDEX -> byte int
          +	 * QNAME -> byte string
          +	 * UUID ::= byte[16]
          +	 * LAST_MODIFIED ::= byte[8]
          +	 * 
          + *

          + */ + 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); + } + + @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; + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java new file mode 100644 index 0000000000..12323ed26e --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java @@ -0,0 +1,192 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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(), Integer.toString(MAX_VALUE_SIZE)}); + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java new file mode 100644 index 0000000000..56695af0a7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * 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$ + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java new file mode 100644 index 0000000000..14aae60ea2 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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$ + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java new file mode 100644 index 0000000000..4dad57525e --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java new file mode 100644 index 0000000000..d618e56b46 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java new file mode 100644 index 0000000000..a4a5b045f8 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java @@ -0,0 +1,231 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java new file mode 100644 index 0000000000..ea6375b725 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java new file mode 100644 index 0000000000..c350baafd0 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java @@ -0,0 +1,356 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java new file mode 100644 index 0000000000..a68674fb43 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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$ + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java new file mode 100644 index 0000000000..0b534f48b3 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java @@ -0,0 +1,217 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427, 483862 + *******************************************************************************/ +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.*; + +/** + * 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); + } + + @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); + } + + /** + * @see org.eclipse.core.resources.refresh.IRefreshResult#refresh + */ + public void refresh(IResource resource) { + if (resource == null) + return; + addRequest(resource); + schedule(UPDATE_DELAY); + } + + @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; + SubMonitor subMonitor = SubMonitor.convert(monitor); + try { + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(RefreshManager.DEBUG_PREFIX + " starting refresh job"); //$NON-NLS-1$ + int refreshCount = 0; + int depth = 2; + + IResource toRefresh; + while ((toRefresh = nextRequest()) != null) { + try { + subMonitor.setWorkRemaining(Math.max(fRequests.size(), 100)); + refreshCount++; + long refreshTime = -System.currentTimeMillis(); + toRefresh.refreshLocal(1000 + depth, subMonitor.split(1)); + refreshTime += System.currentTimeMillis(); + if (refreshTime > longestRefresh) + longestRefresh = refreshTime; + //show occasional progress + 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; + 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; + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java new file mode 100644 index 0000000000..4f41ffd281 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java new file mode 100644 index 0000000000..0c5c42c293 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java @@ -0,0 +1,762 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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; + } + } + } + + @Override + public void shutdown(IProgressMonitor monitor) { + workspace.removeResourceChangeListener(this); + locationsMap.clear(); + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java new file mode 100644 index 0000000000..8448a33fbe --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * 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); + } + + @Override + public String getName() { + return name; + } + + @Override + public IProject getProject() { + return project; + } + + @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; + } + + @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; + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java new file mode 100644 index 0000000000..7c750ae17c --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java @@ -0,0 +1,212 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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(); + } + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java new file mode 100644 index 0000000000..d4979aca98 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java @@ -0,0 +1,502 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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); + } + } + + @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; + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java new file mode 100644 index 0000000000..be7a269f9a --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java @@ -0,0 +1,621 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Broadcom Corporation - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = NEXT_VERTEX; + final Integer AFTER_NEXTED_DFS_VISIT_OBJECT = 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 vertexes, List 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 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 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java new file mode 100644 index 0000000000..234f2edff7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java @@ -0,0 +1,387 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java new file mode 100644 index 0000000000..59122719f7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java @@ -0,0 +1,549 @@ +/******************************************************************************* + * 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 + * Sergey Prigogin (Google) - [464072] Refresh on Access ignored during text search + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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); + } + + @Override + public boolean belongsTo(Object family) { + return FAMILY_DESCRIPTION_CACHE_FLUSH.equals(family); + } + + @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) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } + 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 (FileNotFoundException e) { + String message = NLS.bind(Messages.localstore_fileNotFound, file.getFullPath()); + throw new ResourceException(IResourceStatus.RESOURCE_NOT_FOUND, file.getFullPath(), message, e); + } 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java new file mode 100644 index 0000000000..e45d735926 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java new file mode 100644 index 0000000000..fcb7ef2f15 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java @@ -0,0 +1,444 @@ +/******************************************************************************* + * 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 + * Sergey Prigogin (Google) - [464072] Refresh on Access ignored during text search + *******************************************************************************/ +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); + try { + return internalGetCharset(checkImplicit, info); + } catch (CoreException e) { + if (e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND) { + return checkImplicit ? workspace.getCharsetManager().getCharsetFor(getFullPath().removeLastSegments(1), true) : null; + } + throw e; + } + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java new file mode 100644 index 0000000000..d617833117 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * 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; + } + + @Override + public boolean exists() { + return store.exists(this); + } + + @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); + } + } + + @Override + public InputStream getContents() throws CoreException { + return store.getContents(this); + } + + @Override + public IPath getFullPath() { + return fullPath; + } + + @Override + public long getModificationTime() { + return lastModified; + } + + @Override + public String getName() { + return fullPath.lastSegment(); + } + + public UniversalUniqueIdentifier getUUID() { + return uuid; + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Filter.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java new file mode 100644 index 0000000000..88cd8d795b --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java new file mode 100644 index 0000000000..d6cf7fcf8d --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * 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$ + } + + @Override + public String getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getArgumentType() { + return argumentType; + } + + public AbstractFileInfoMatcher createFilter() { + try { + return (AbstractFileInfoMatcher) element.createExecutableExtension("class"); //$NON-NLS-1$ + } catch (CoreException e) { + Policy.log(e); + return null; + } + } + + @Override + public boolean isFirstOrdering() { + return isFirst; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java new file mode 100644 index 0000000000..78ba4d5a01 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java new file mode 100644 index 0000000000..3f737a4de6 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java new file mode 100644 index 0000000000..c82bfb9dc8 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IManager.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IMarkerSetElement.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java new file mode 100644 index 0000000000..5db12b9fd9 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * 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 {@link 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 { + /** + * Internal implementation of {@link TeamHook#setRuleFactory(IProject, IResourceRuleFactory)}. + */ + @SuppressWarnings("javadoc") // Suppress the "method in not visible" warning. + protected void setRuleFactory(IProject project, IResourceRuleFactory factory) { + Workspace workspace = ((Workspace) project.getWorkspace()); + ((Rules) workspace.getRuleFactory()).setRuleFactory(project, factory); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalWorkspaceJob.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java new file mode 100644 index 0000000000..070d3d9b5d --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java new file mode 100644 index 0000000000..2ec5c47588 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java @@ -0,0 +1,514 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * 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 + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = Integer.parseInt(sequenceNumber) + 1; + n = n < 0 ? 1 : n; + sequenceNumber = Integer.toString(n); + getWorkspace().getSaveManager().getMasterTable().setProperty(key.toString(), sequenceNumber); + } + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java new file mode 100644 index 0000000000..0a3a68a0d7 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java new file mode 100644 index 0000000000..880dd53dd4 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java @@ -0,0 +1,340 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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, Integer.valueOf(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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java new file mode 100644 index 0000000000..bac49bfb96 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java @@ -0,0 +1,295 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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); + } + + @Override + public void clear() { + count = 0; + elements = EMPTY; + } + + @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; + } + + @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; + } + + /** + * 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(); + } + + @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; + } + + @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; + } + + @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; + } + + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * 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; + } + + @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; + } + + @Override + public void putAll(Map map) { + for (Map.Entry e : map.entrySet()) + put(e.getKey(), e.getValue()); + } + + @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; + } + + @Override + public int size() { + return count; + } + + @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; + } + + /** + * 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java new file mode 100644 index 0000000000..d892c2ef52 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java @@ -0,0 +1,210 @@ +/******************************************************************************* + * 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; + } + + @Override + public Object getAttribute(String attributeName) { + return info.getAttribute(attributeName); + } + + @Override + public int getAttribute(String attributeName, int defaultValue) { + Object value = info.getAttribute(attributeName); + if (value instanceof Integer) + return ((Integer) value).intValue(); + return defaultValue; + } + + @Override + public String getAttribute(String attributeName, String defaultValue) { + Object value = info.getAttribute(attributeName); + if (value instanceof String) + return (String) value; + return defaultValue; + } + + @Override + public boolean getAttribute(String attributeName, boolean defaultValue) { + Object value = info.getAttribute(attributeName); + if (value instanceof Boolean) + return ((Boolean) value).booleanValue(); + return defaultValue; + } + + @Override + public Map getAttributes() { + return info.getAttributes(); + } + + @Override + public Object[] getAttributes(String[] attributeNames) { + return info.getAttributes(attributeNames); + } + + @Override + public long getId() { + return info.getId(); + } + + @Override + public int getKind() { + return kind; + } + + @Override + public IMarker getMarker() { + return new Marker(resource, getId()); + } + + @Override + public IResource getResource() { + return resource; + } + + @Override + public String getType() { + return info.getType(); + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java new file mode 100644 index 0000000000..fb77faf894 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java new file mode 100644 index 0000000000..d0d2718311 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java @@ -0,0 +1,211 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488938, 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.UnsupportedEncodingException; +import java.util.Map; +import java.util.Map.Entry; +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 = 1; + protected static final Integer INTEGER_TWO = 2; + protected static final Integer INTEGER_ZERO = 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 (Entry entry : map.entrySet()) { + Object key = entry.getKey(); + Assert.isTrue(key instanceof String); + Object value = entry.getValue(); + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java new file mode 100644 index 0000000000..59f823a7ac --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java @@ -0,0 +1,669 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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; + } + + /** + * 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); + } + + @Override + public void shutdown(IProgressMonitor monitor) { + // do nothing + } + + public void snap(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { + writer.snap(info, requestor, output); + } + + @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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java new file mode 100644 index 0000000000..b14d82cd5b --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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, 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java new file mode 100644 index 0000000000..775f2acf4e --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = input.readInt(); + break; + case ATTRIBUTE_BOOLEAN : + value = input.readBoolean(); + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java new file mode 100644 index 0000000000..755da7b188 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = input.readInt(); + break; + case ATTRIBUTE_BOOLEAN : + value = input.readBoolean(); + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java new file mode 100644 index 0000000000..0400cf8a3d --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = intValue; + } + break; + case ATTRIBUTE_BOOLEAN : + value = input.readBoolean(); + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSet.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java new file mode 100644 index 0000000000..bf980b2ad4 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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, 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java new file mode 100644 index 0000000000..583876eb69 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = input.readInt(); + break; + case ATTRIBUTE_BOOLEAN : + value = input.readBoolean(); + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java new file mode 100644 index 0000000000..d7f030e2aa --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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<>(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(); + 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 = intValue; + } + break; + case ATTRIBUTE_BOOLEAN : + value = input.readBoolean(); + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java new file mode 100644 index 0000000000..6a981c9213 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; + +public class MarkerTypeDefinitionCache { + static class MarkerTypeDefinition { + boolean isPersistent = false; + Set superTypes; + + MarkerTypeDefinition(IExtension ext) { + IConfigurationElement[] elements = ext.getConfigurationElements(); + for (int i = 0; i < elements.length; i++) { + IConfigurationElement element = elements[i]; + // supertype + final String elementName = element.getName(); + if (elementName.equalsIgnoreCase("super")) { //$NON-NLS-1$ + String aType = element.getAttribute("type"); //$NON-NLS-1$ + if (aType != null) { + if (superTypes == null) + superTypes = new HashSet<>(8); + //note that all marker type names will be in the intern table + //already because there is invariably a constant to describe + //the type name + superTypes.add(aType.intern()); + } + } + // persistent + if (elementName.equalsIgnoreCase("persistent")) { //$NON-NLS-1$ + String bool = element.getAttribute("value"); //$NON-NLS-1$ + if (bool != null) + this.isPersistent = Boolean.valueOf(bool).booleanValue(); + } + // XXX: legacy code for support of tag. remove later. + if (elementName.equalsIgnoreCase("transient")) { //$NON-NLS-1$ + String bool = element.getAttribute("value"); //$NON-NLS-1$ + if (bool != null) + this.isPersistent = !Boolean.valueOf(bool).booleanValue(); + } + } + } + } + + /** + * The marker type definitions. Maps String (markerId) -> MarkerTypeDefinition + */ + protected HashMap definitions; + + /** Constructs a new type cache. + */ + public MarkerTypeDefinitionCache() { + loadDefinitions(); + HashSet toCompute = new HashSet<>(definitions.keySet()); + for (Iterator i = definitions.keySet().iterator(); i.hasNext();) { + String markerId = i.next(); + if (toCompute.contains(markerId)) + computeSuperTypes(markerId, toCompute); + } + } + + /** + * Computes the transitive set of super types of the given marker type. + * @param markerId The type to compute super types for + * @param toCompute The set of types that have not yet had their + * supertypes computed. + * @return The transitive set of super types for this marker, or null + * if this marker is not defined or has no super types. + */ + private Set computeSuperTypes(String markerId, Set toCompute) { + MarkerTypeDefinition def = definitions.get(markerId); + if (def == null || def.superTypes == null) { + //nothing to do if there are no supertypes + toCompute.remove(markerId); + return null; + } + Set transitiveSuperTypes = new HashSet<>(def.superTypes); + for (Iterator it = def.superTypes.iterator(); it.hasNext();) { + String superId = it.next(); + Set toAdd = null; + if (toCompute.contains(superId)) { + //this type's super types have not been compute yet. Do recursive call + toAdd = computeSuperTypes(superId, toCompute); + } else { + // we have already computed this super type's super types (or it doesn't exist) + MarkerTypeDefinition parentDef = definitions.get(superId); + if (parentDef != null) + toAdd = parentDef.superTypes; + } + if (toAdd != null) + transitiveSuperTypes.addAll(toAdd); + } + def.superTypes = transitiveSuperTypes; + toCompute.remove(markerId); + return transitiveSuperTypes; + } + + /** + * Returns true if the given marker type is defined to be persistent. + */ + public boolean isPersistent(String type) { + MarkerTypeDefinition def = definitions.get(type); + return def != null && def.isPersistent; + } + + /** + * Returns true if the given target class has the specified type as a super type. + */ + public boolean isSubtype(String type, String superType) { + //types are considered super types of themselves + if (type.equals(superType)) + return true; + MarkerTypeDefinition def = definitions.get(type); + return def != null && def.superTypes != null && def.superTypes.contains(superType); + } + + private void loadDefinitions() { + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MARKERS); + IExtension[] types = point.getExtensions(); + definitions = new HashMap<>(types.length); + for (int i = 0; i < types.length; i++) { + String markerId = types[i].getUniqueIdentifier(); + if (markerId != null) + definitions.put(markerId.intern(), new MarkerTypeDefinition(types[i])); + else + Policy.log(IStatus.WARNING, "Missing marker id from plugin: " + types[i].getContributor().getName(), null); //$NON-NLS-1$ + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java new file mode 100644 index 0000000000..7cf7af0aaf --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; +import org.eclipse.core.internal.watson.IPathRequestor; + +// +public class MarkerWriter { + + protected MarkerManager manager; + + // version numbers + public static final int MARKERS_SAVE_VERSION = 3; + public static final int MARKERS_SNAP_VERSION = 2; + + // 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 MarkerWriter(MarkerManager manager) { + super(); + this.manager = manager; + } + + /** + * Returns an Object array of length 2. The first element is an Integer which is the number + * of persistent markers found. The second element is an array of boolean values, with a + * value of true meaning that the marker at that index is to be persisted. + */ + private Object[] filterMarkers(IMarkerSetElement[] markers) { + Object[] result = new Object[2]; + boolean[] isPersistent = new boolean[markers.length]; + int count = 0; + for (int i = 0; i < markers.length; i++) { + MarkerInfo info = (MarkerInfo) markers[i]; + if (manager.isPersistent(info)) { + isPersistent[i] = true; + count++; + } + } + result[0] = count; + result[1] = isPersistent; + return result; + } + + /** + * 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 + * + */ + public void save(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List writtenTypes) throws IOException { + // phantom resources don't have markers + if (info.isSet(ICoreConstants.M_PHANTOM)) + return; + MarkerSet markers = info.getMarkers(false); + if (markers == null) + return; + IMarkerSetElement[] elements = markers.elements(); + // filter out the markers...determine if there are any persistent ones + Object[] result = filterMarkers(elements); + int count = ((Integer) result[0]).intValue(); + if (count == 0) + return; + // if this is the first set of markers that we have written, then + // write the version id for the file. + if (output.size() == 0) + output.writeInt(MARKERS_SAVE_VERSION); + boolean[] isPersistent = (boolean[]) result[1]; + output.writeUTF(requestor.requestPath().toString()); + output.writeInt(count); + for (int i = 0; i < elements.length; i++) + if (isPersistent[i]) + write((MarkerInfo) elements[i], output, writtenTypes); + } + + /** + * Snapshot the markers for the specified resource to the given output stream. + * + * 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 + */ + public void snap(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { + // phantom resources don't have markers + if (info.isSet(ICoreConstants.M_PHANTOM)) + return; + if (!info.isSet(ICoreConstants.M_MARKERS_SNAP_DIRTY)) + return; + MarkerSet markers = info.getMarkers(false); + if (markers == null) + return; + IMarkerSetElement[] elements = markers.elements(); + // filter out the markers...determine if there are any persistent ones + Object[] result = filterMarkers(elements); + int count = ((Integer) result[0]).intValue(); + // write the version id for the snapshot. + output.writeInt(MARKERS_SNAP_VERSION); + boolean[] isPersistent = (boolean[]) result[1]; + output.writeUTF(requestor.requestPath().toString()); + // always write out the count...even if its zero. this will help + // use pick up marker deletions from our snapshot. + output.writeInt(count); + List writtenTypes = new ArrayList<>(); + for (int i = 0; i < elements.length; i++) + if (isPersistent[i]) + write((MarkerInfo) elements[i], output, writtenTypes); + info.clear(ICoreConstants.M_MARKERS_SNAP_DIRTY); + } + + /* + * Write out the given marker attributes to the given output stream. + */ + private void write(Map attributes, DataOutputStream output) throws IOException { + output.writeShort(attributes.size()); + for (Map.Entry e : attributes.entrySet()) { + String key = e.getKey(); + output.writeUTF(key); + Object value = e.getValue(); + if (value instanceof Integer) { + output.writeByte(ATTRIBUTE_INTEGER); + output.writeInt(((Integer) value).intValue()); + continue; + } + if (value instanceof Boolean) { + output.writeByte(ATTRIBUTE_BOOLEAN); + output.writeBoolean(((Boolean) value).booleanValue()); + continue; + } + if (value instanceof String) { + output.writeByte(ATTRIBUTE_STRING); + output.writeUTF((String) value); + continue; + } + // otherwise we came across an attribute of an unknown type + // so just write out null since we don't know how to marshal it. + output.writeByte(ATTRIBUTE_NULL); + } + } + + private void write(MarkerInfo info, DataOutputStream output, List writtenTypes) throws IOException { + output.writeLong(info.getId()); + // if we have already written the type once, then write an integer + // constant to represent it instead to remove duplication + String type = info.getType(); + int index = writtenTypes.indexOf(type); + if (index == -1) { + output.writeByte(QNAME); + output.writeUTF(type); + writtenTypes.add(type); + } else { + output.writeByte(INDEX); + output.writeInt(index); + } + + // write out the size of the attribute table and + // then each attribute. + if (info.getAttributes(false) == null) { + output.writeShort(0); + } else + write(info.getAttributes(false), output); + + // write out the creation time + output.writeLong(info.getCreationTime()); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java new file mode 100644 index 0000000000..e60a839f17 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java @@ -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 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +public abstract class ModelObject implements Cloneable { + protected String name; + + public ModelObject() { + super(); + } + + public ModelObject(String name) { + setName(name); + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; // won't happen + } + } + + public String getName() { + return name; + } + + public void setName(String value) { + name = value; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java new file mode 100644 index 0000000000..60eb239fbe --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java @@ -0,0 +1,306 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.internal.localstore.SafeFileOutputStream; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; + +public class ModelObjectWriter implements IModelObjectConstants { + + /** + * Returns the string representing the serialized set of build triggers for + * the given command + */ + private static String triggerString(BuildCommand command) { + StringBuffer buf = new StringBuffer(); + if (command.isBuilding(IncrementalProjectBuilder.AUTO_BUILD)) + buf.append(TRIGGER_AUTO).append(','); + if (command.isBuilding(IncrementalProjectBuilder.CLEAN_BUILD)) + buf.append(TRIGGER_CLEAN).append(','); + if (command.isBuilding(IncrementalProjectBuilder.FULL_BUILD)) + buf.append(TRIGGER_FULL).append(','); + if (command.isBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD)) + buf.append(TRIGGER_INCREMENTAL).append(','); + return buf.toString(); + } + + public ModelObjectWriter() { + super(); + } + + protected String[] getReferencedProjects(ProjectDescription description) { + IProject[] projects = description.getReferencedProjects(); + String[] result = new String[projects.length]; + for (int i = 0; i < projects.length; i++) + result[i] = projects[i].getName(); + return result; + } + + protected void write(BuildCommand command, XMLWriter writer) { + writer.startTag(BUILD_COMMAND, null); + if (command != null) { + writer.printSimpleTag(NAME, command.getName()); + if (shouldWriteTriggers(command)) + writer.printSimpleTag(BUILD_TRIGGERS, triggerString(command)); + write(ARGUMENTS, command.getArguments(false), writer); + } + writer.endTag(BUILD_COMMAND); + } + + /** + * Returns whether the build triggers for this command should be written. + */ + private boolean shouldWriteTriggers(BuildCommand command) { + //only write triggers if command is configurable and there exists a trigger + //that the builder does NOT respond to. I.e., don't write out on the default + //cases to avoid dirtying .project files unnecessarily. + if (!command.isConfigurable()) + return false; + return !command.isBuilding(IncrementalProjectBuilder.AUTO_BUILD) || !command.isBuilding(IncrementalProjectBuilder.CLEAN_BUILD) || !command.isBuilding(IncrementalProjectBuilder.FULL_BUILD) || !command.isBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD); + } + + protected void write(LinkDescription description, XMLWriter writer) { + writer.startTag(LINK, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getProjectRelativePath()); + writer.printSimpleTag(TYPE, Integer.toString(description.getType())); + //use ASCII string of URI to ensure spaces are encoded + writeLocation(description.getLocationURI(), writer); + } + writer.endTag(LINK); + } + + protected void write(IResourceFilterDescription description, XMLWriter writer) { + writer.startTag(FILTER, null); + if (description != null) { + writer.printSimpleTag(ID, ((FilterDescription) description).getId()); + writer.printSimpleTag(NAME, description.getResource().getProjectRelativePath()); + writer.printSimpleTag(TYPE, Integer.toString(description.getType())); + if (description.getFileInfoMatcherDescription() != null) { + write(description.getFileInfoMatcherDescription(), writer); + } + } + writer.endTag(FILTER); + } + + protected void write(FileInfoMatcherDescription description, XMLWriter writer) { + writer.startTag(MATCHER, null); + writer.printSimpleTag(ID, description.getId()); + if (description.getArguments() != null) { + if (description.getArguments() instanceof String) { + writer.printSimpleTag(ARGUMENTS, description.getArguments()); + } else if (description.getArguments() instanceof FileInfoMatcherDescription[]) { + writer.startTag(ARGUMENTS, null); + FileInfoMatcherDescription[] array = (FileInfoMatcherDescription[]) description.getArguments(); + for (int i = 0; i < array.length; i++) { + write(array[i], writer); + } + writer.endTag(ARGUMENTS); + } else + writer.printSimpleTag(ARGUMENTS, ""); //$NON-NLS-1$ + } + writer.endTag(MATCHER); + } + + protected void write(VariableDescription description, XMLWriter writer) { + writer.startTag(VARIABLE, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getName()); + writer.printSimpleTag(VALUE, description.getValue()); + } + writer.endTag(VARIABLE); + } + + /** + * Writes a location to the XML writer. For backwards compatibility, + * local file system locations are written and read using a different tag + * from non-local file systems. + * @param location + * @param writer + */ + private void writeLocation(URI location, XMLWriter writer) { + if (EFS.SCHEME_FILE.equals(location.getScheme())) { + writer.printSimpleTag(LOCATION, FileUtil.toPath(location).toPortableString()); + } else { + writer.printSimpleTag(LOCATION_URI, location.toASCIIString()); + } + } + + /** + * The parameter tempLocation is a location to place our temp file (copy of the target one) + * to be used in case we could not successfully write the new file. + */ + public void write(Object object, IPath location, IPath tempLocation, String lineSeparator) throws IOException { + SafeFileOutputStream file = null; + String tempPath = tempLocation == null ? null : tempLocation.toOSString(); + try { + file = new SafeFileOutputStream(location.toOSString(), tempPath); + write(object, file, lineSeparator); + file.close(); + } finally { + FileUtil.safeClose(file); + } + } + + /** + * The OutputStream is closed in this method. + */ + public void write(Object object, OutputStream output, String lineSeparator) throws IOException { + try { + XMLWriter writer = new XMLWriter(output, lineSeparator); + write(object, writer); + writer.flush(); + writer.close(); + if (writer.checkError()) + throw new IOException(); + } finally { + FileUtil.safeClose(output); + } + } + + protected void write(Object obj, XMLWriter writer) throws IOException { + if (obj instanceof BuildCommand) { + write((BuildCommand) obj, writer); + return; + } + if (obj instanceof ProjectDescription) { + write((ProjectDescription) obj, writer); + return; + } + if (obj instanceof WorkspaceDescription) { + write((WorkspaceDescription) obj, writer); + return; + } + if (obj instanceof LinkDescription) { + write((LinkDescription) obj, writer); + return; + } + if (obj instanceof IResourceFilterDescription) { + write((IResourceFilterDescription) obj, writer); + return; + } + if (obj instanceof VariableDescription) { + write((VariableDescription) obj, writer); + return; + } + writer.printTabulation(); + writer.println(obj.toString()); + } + + protected void write(ProjectDescription description, XMLWriter writer) throws IOException { + writer.startTag(PROJECT_DESCRIPTION, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getName()); + String comment = description.getComment(); + writer.printSimpleTag(COMMENT, comment == null ? "" : comment); //$NON-NLS-1$ + URI snapshotLocation = description.getSnapshotLocationURI(); + if (snapshotLocation != null) { + writer.printSimpleTag(SNAPSHOT_LOCATION, snapshotLocation.toString()); + } + write(PROJECTS, PROJECT, getReferencedProjects(description), writer); + write(BUILD_SPEC, Arrays.asList(description.getBuildSpec(false)), writer); + write(NATURES, NATURE, description.getNatureIds(false), writer); + HashMap links = description.getLinks(); + if (links != null) { + // ensure consistent order of map elements + List sorted = new ArrayList<>(links.values()); + Collections.sort(sorted); + write(LINKED_RESOURCES, sorted, writer); + } + HashMap> filters = description.getFilters(); + if (filters != null) { + List sorted = new ArrayList<>(); + for (Iterator> it = filters.values().iterator(); it.hasNext();) { + List list = it.next(); + sorted.addAll(list); + } + Collections.sort(sorted); + write(FILTERED_RESOURCES, sorted, writer); + } + HashMap variables = description.getVariables(); + if (variables != null) { + List sorted = new ArrayList<>(variables.values()); + Collections.sort(sorted); + write(VARIABLE_LIST, sorted, writer); + } + } + writer.endTag(PROJECT_DESCRIPTION); + } + + protected void write(String name, Collection collection, XMLWriter writer) throws IOException { + writer.startTag(name, null); + for (Object o : collection) + write(o, writer); + writer.endTag(name); + } + + /** + * Write maps of (String, String). + */ + protected void write(String name, Map table, XMLWriter writer) { + writer.startTag(name, null); + if (table != null) { + // ensure consistent order of map elements + List sorted = new ArrayList<>(table.keySet()); + Collections.sort(sorted); + + for (Iterator it = sorted.iterator(); it.hasNext();) { + String key = it.next(); + Object value = table.get(key); + writer.startTag(DICTIONARY, null); + { + writer.printSimpleTag(KEY, key); + writer.printSimpleTag(VALUE, value); + } + writer.endTag(DICTIONARY); + } + } + writer.endTag(name); + } + + protected void write(String name, String elementTagName, String[] array, XMLWriter writer) { + writer.startTag(name, null); + for (int i = 0; i < array.length; i++) + writer.printSimpleTag(elementTagName, array[i]); + writer.endTag(name); + } + + protected void write(WorkspaceDescription description, XMLWriter writer) { + writer.startTag(WORKSPACE_DESCRIPTION, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getName()); + writer.printSimpleTag(AUTOBUILD, description.isAutoBuilding() ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$ + writer.printSimpleTag(SNAPSHOT_INTERVAL, description.getSnapshotInterval()); + writer.printSimpleTag(APPLY_FILE_STATE_POLICY, description.isApplyFileStatePolicy() ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$ + writer.printSimpleTag(FILE_STATE_LONGEVITY, description.getFileStateLongevity()); + writer.printSimpleTag(MAX_FILE_STATE_SIZE, description.getMaxFileStateSize()); + writer.printSimpleTag(MAX_FILE_STATES, description.getMaxFileStates()); + String[] order = description.getBuildOrder(false); + if (order != null) + write(BUILD_ORDER, PROJECT, order, writer); + } + writer.endTag(WORKSPACE_DESCRIPTION); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java new file mode 100644 index 0000000000..4b369fa4a7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IMoveDeleteHook; +import org.eclipse.core.resources.team.IResourceTree; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * @since 2.0 + */ +public class MoveDeleteHook implements IMoveDeleteHook { + + /** + * @see IMoveDeleteHook#deleteFile(IResourceTree, IFile, int, IProgressMonitor) + */ + @Override + public boolean deleteFile(IResourceTree tree, IFile file, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#deleteFolder(IResourceTree, IFolder, int, IProgressMonitor) + */ + @Override + public boolean deleteFolder(IResourceTree tree, IFolder folder, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#deleteProject(IResourceTree, IProject, int, IProgressMonitor) + */ + @Override + public boolean deleteProject(IResourceTree tree, IProject project, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#moveFile(IResourceTree, IFile, IFile, int, IProgressMonitor) + */ + @Override + public boolean moveFile(IResourceTree tree, IFile source, IFile destination, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#moveFolder(IResourceTree, IFolder, IFolder, int, IProgressMonitor) + */ + @Override + public boolean moveFolder(final IResourceTree tree, IFolder source, IFolder destination, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#moveProject(IResourceTree, IProject, IProjectDescription, int, IProgressMonitor) + */ + @Override + public boolean moveProject(IResourceTree tree, IProject source, IProjectDescription description, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java new file mode 100644 index 0000000000..d14a368ae7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java @@ -0,0 +1,643 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.internal.events.ILifecycleListener; +import org.eclipse.core.internal.events.LifecycleEvent; +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; + +/** + * Maintains collection of known nature descriptors, and implements + * nature-related algorithms provided by the workspace. + */ +public class NatureManager implements ILifecycleListener, IManager { + //maps String (nature ID) -> descriptor objects + private Map descriptors; + + //maps IProject -> String[] of enabled natures for that project + private final Map natureEnablements = new HashMap<>(20); + + //maps String (builder ID) -> String (nature ID) + private Map buildersToNatures; + //colour constants used in cycle detection algorithm + private static final byte WHITE = 0; + private static final byte GREY = 1; + private static final byte BLACK = 2; + + protected NatureManager() { + super(); + } + + /** + * Computes the list of natures that are enabled for the given project. + * Enablement computation is subtly different from nature set + * validation, because it must find and remove all inconsistencies. + */ + protected String[] computeNatureEnablements(Project project) { + final ProjectDescription description = project.internalGetDescription(); + if (description == null) + return new String[0];//project deleted concurrently + String[] natureIds = description.getNatureIds(); + int count = natureIds.length; + if (count == 0) + return natureIds; + + //set of the nature ids being validated (String (id)) + HashSet candidates = new HashSet<>(count * 2); + //table of String(set ID) -> ArrayList (nature IDs that belong to that set) + HashMap> setsToNatures = new HashMap<>(count); + for (int i = 0; i < count; i++) { + String id = natureIds[i]; + ProjectNatureDescriptor desc = (ProjectNatureDescriptor) getNatureDescriptor(id); + if (desc == null) + continue; + if (!desc.hasCycle) + candidates.add(id); + //build set to nature map + String[] setIds = desc.getNatureSetIds(); + for (int j = 0; j < setIds.length; j++) { + String set = setIds[j]; + ArrayList current = setsToNatures.get(set); + if (current == null) { + current = new ArrayList<>(5); + setsToNatures.put(set, current); + } + current.add(id); + } + } + //now remove all natures that belong to sets with more than one member + for (Iterator> it = setsToNatures.values().iterator(); it.hasNext();) { + ArrayList setMembers = it.next(); + if (setMembers.size() > 1) { + candidates.removeAll(setMembers); + } + } + //now walk over the set and ensure all pre-requisite natures are present + //need to walk in prereq order because if A requires B and B requires C, and C is + //disabled for some other reason, we must ensure both A and B are disabled + String[] orderedCandidates = candidates.toArray(new String[candidates.size()]); + orderedCandidates = sortNatureSet(orderedCandidates); + for (int i = 0; i < orderedCandidates.length; i++) { + String id = orderedCandidates[i]; + IProjectNatureDescriptor desc = getNatureDescriptor(id); + String[] required = desc.getRequiredNatureIds(); + for (int j = 0; j < required.length; j++) { + if (!candidates.contains(required[j])) { + candidates.remove(id); + break; + } + } + } + //remaining candidates are enabled + return candidates.toArray(new String[candidates.size()]); + } + + /* (non-Javadoc) + * @see IWorkspace#getNatureDescriptor(String) + */ + public synchronized IProjectNatureDescriptor getNatureDescriptor(String natureId) { + lazyInitialize(); + return descriptors.get(natureId); + } + + /* (non-Javadoc) + * @see IWorkspace#getNatureDescriptors() + */ + public synchronized IProjectNatureDescriptor[] getNatureDescriptors() { + lazyInitialize(); + Collection values = descriptors.values(); + return values.toArray(new IProjectNatureDescriptor[values.size()]); + } + + @Override + public void handleEvent(LifecycleEvent event) { + switch (event.kind) { + case LifecycleEvent.POST_PROJECT_CHANGE : + case LifecycleEvent.PRE_PROJECT_CLOSE : + case LifecycleEvent.PRE_PROJECT_DELETE : + case LifecycleEvent.PRE_PROJECT_MOVE : + case LifecycleEvent.PRE_PROJECT_OPEN : + flushEnablements((IProject) event.resource); + } + } + + /** + * Configures the nature with the given ID for the given project. + */ + protected void configureNature(final Project project, final String natureID, final MultiStatus errors) { + ISafeRunnable code = new ISafeRunnable() { + @Override + public void run() throws Exception { + IProjectNature nature = createNature(project, natureID); + nature.configure(); + ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, true); + info.setNature(natureID, nature); + } + + @Override + public void handleException(Throwable exception) { + if (exception instanceof CoreException) + errors.add(((CoreException) exception).getStatus()); + else + errors.add(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, project.getFullPath(), NLS.bind(Messages.resources_errorNature, natureID), exception)); + } + }; + if (Policy.DEBUG_NATURES) { + Policy.debug("Configuring nature: " + natureID + " on project: " + project.getName()); //$NON-NLS-1$ //$NON-NLS-2$ + } + SafeRunner.run(code); + } + + /** + * Configures the natures for the given project. Natures found in the new description + * that weren't present in the old description are added, and natures missing from the + * new description are removed. Updates the old description so that it reflects + * the new set of the natures. Errors are added to the given multi-status. + */ + @SuppressWarnings({"unchecked"}) + public void configureNatures(Project project, ProjectDescription oldDescription, ProjectDescription newDescription, MultiStatus status) { + // Be careful not to rely on much state because (de)configuring a nature + // may well result in recursive calls to this method. + HashSet oldNatures = new HashSet<>(Arrays.asList(oldDescription.getNatureIds(false))); + HashSet newNatures = new HashSet<>(Arrays.asList(newDescription.getNatureIds(false))); + if (oldNatures.equals(newNatures)) + return; + HashSet deletions = (HashSet) oldNatures.clone(); + HashSet additions = (HashSet) newNatures.clone(); + additions.removeAll(oldNatures); + deletions.removeAll(newNatures); + //do validation of the changes. If any single change is invalid, fail the whole operation + IStatus result = validateAdditions(newNatures, additions, project); + if (!result.isOK()) { + status.merge(result); + return; + } + result = validateRemovals(newNatures, deletions); + if (!result.isOK()) { + status.merge(result); + return; + } + // set the list of nature ids BEFORE (de)configuration so recursive calls will + // not try to do the same work. + oldDescription.setNatureIds(newDescription.getNatureIds(true)); + flushEnablements(project); + //(de)configure in topological order to maintain consistency of configured set + String[] ordered = null; + if (deletions.size() > 0) { + ordered = sortNatureSet(deletions.toArray(new String[deletions.size()])); + for (int i = ordered.length; --i >= 0;) + deconfigureNature(project, ordered[i], status); + } + if (additions.size() > 0) { + ordered = sortNatureSet(additions.toArray(new String[additions.size()])); + for (int i = 0; i < ordered.length; i++) + configureNature(project, ordered[i], status); + } + } + + /** + * Finds the nature extension, and initializes and returns an instance. + */ + protected IProjectNature createNature(Project project, String natureID) throws CoreException { + IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_NATURES, natureID); + if (extension == null) { + String message = NLS.bind(Messages.resources_natureExtension, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null); + } + IConfigurationElement[] configs = extension.getConfigurationElements(); + if (configs.length < 1) { + String message = NLS.bind(Messages.resources_natureClass, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null); + } + //find the runtime configuration element + IConfigurationElement config = null; + for (int i = 0; config == null && i < configs.length; i++) + if ("runtime".equalsIgnoreCase(configs[i].getName())) //$NON-NLS-1$ + config = configs[i]; + if (config == null) { + String message = NLS.bind(Messages.resources_natureFormat, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null); + } + try { + IProjectNature nature = (IProjectNature) config.createExecutableExtension("run"); //$NON-NLS-1$ + nature.setProject(project); + return nature; + } catch (ClassCastException e) { + String message = NLS.bind(Messages.resources_natureImplement, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, e); + } + } + + /** + * Deconfigures the nature with the given ID for the given project. + */ + protected void deconfigureNature(final Project project, final String natureID, final MultiStatus status) { + final ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, true); + IProjectNature existingNature = info.getNature(natureID); + if (existingNature == null) { + // if there isn't a nature then create one so we can deconfig it. + try { + existingNature = createNature(project, natureID); + } catch (CoreException e) { + // Ignore - we are removing a nature that no longer exists in the install + return; + } + } + final IProjectNature nature = existingNature; + ISafeRunnable code = new ISafeRunnable() { + @Override + public void run() throws Exception { + nature.deconfigure(); + info.setNature(natureID, null); + } + + @Override + public void handleException(Throwable exception) { + if (exception instanceof CoreException) + status.add(((CoreException) exception).getStatus()); + else + status.add(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, project.getFullPath(), NLS.bind(Messages.resources_natureDeconfig, natureID), exception)); + } + }; + if (Policy.DEBUG_NATURES) { + Policy.debug("Deconfiguring nature: " + natureID + " on project: " + project.getName()); //$NON-NLS-1$ //$NON-NLS-2$ + } + SafeRunner.run(code); + } + + /** + * Marks all nature descriptors that are involved in cycles + */ + private void detectCycles() { + Collection values = descriptors.values(); + ProjectNatureDescriptor[] natures = values.toArray(new ProjectNatureDescriptor[values.size()]); + for (int i = 0; i < natures.length; i++) + if (natures[i].colour == WHITE) + hasCycles(natures[i]); + } + + /** + * Returns a status indicating failure to configure natures. + */ + protected IStatus failure(String reason) { + return new ResourceStatus(IResourceStatus.INVALID_NATURE_SET, reason); + } + + /** + * Returns the ID of the project nature that claims ownership of the + * builder with the given ID. Returns null if no nature owns that builder. + */ + public synchronized String findNatureForBuilder(String builderID) { + if (buildersToNatures == null) { + buildersToNatures = new HashMap<>(10); + IProjectNatureDescriptor[] descs = getNatureDescriptors(); + for (int i = 0; i < descs.length; i++) { + String natureId = descs[i].getNatureId(); + String[] builders = ((ProjectNatureDescriptor) descs[i]).getBuilderIds(); + for (int j = 0; j < builders.length; j++) { + //FIXME: how to handle multiple natures specifying same builder + buildersToNatures.put(builders[j], natureId); + } + } + } + return buildersToNatures.get(builderID); + } + + private synchronized void flushEnablements(IProject project) { + natureEnablements.remove(project); + } + + /** + * Returns the cached array of enabled natures for this project, + * or null if there is nothing in the cache. + */ + protected synchronized String[] getEnabledNatures(Project project) { + String[] enabled = natureEnablements.get(project); + if (enabled != null) + return enabled; + enabled = computeNatureEnablements(project); + natureEnablements.put(project, enabled); + return enabled; + } + + /** + * Returns true if there are cycles in the graph of nature + * dependencies starting at root i. Returns false otherwise. + * Marks all descriptors that are involved in the cycle as invalid. + */ + protected boolean hasCycles(ProjectNatureDescriptor desc) { + if (desc.colour == BLACK) { + //this subgraph has already been traversed, so we know the answer + return desc.hasCycle; + } + //if we are already grey, then we have found a cycle + if (desc.colour == GREY) { + desc.hasCycle = true; + desc.colour = BLACK; + return true; + } + //colour current descriptor GREY to indicate it is being visited + desc.colour = GREY; + + //visit all dependents of nature i + String[] required = desc.getRequiredNatureIds(); + for (int i = 0; i < required.length; i++) { + ProjectNatureDescriptor dependency = (ProjectNatureDescriptor) getNatureDescriptor(required[i]); + //missing dependencies cannot create cycles + if (dependency != null && hasCycles(dependency)) { + desc.hasCycle = true; + desc.colour = BLACK; + return true; + } + } + desc.hasCycle = false; + desc.colour = BLACK; + return false; + } + + /** + * Returns true if the given project has linked resources, and false otherwise. + */ + protected boolean hasLinks(IProject project) { + try { + IResource[] children = project.members(); + for (int i = 0; i < children.length; i++) + if (children[i].isLinked()) + return true; + } catch (CoreException e) { + //not possible for project to be inaccessible + Policy.log(e.getStatus()); + } + return false; + } + + /** + * Checks if the two natures have overlapping "one-of-nature" set + * memberships. Returns the name of one such overlap, or null if + * there is no set overlap. + */ + protected String hasSetOverlap(IProjectNatureDescriptor one, IProjectNatureDescriptor two) { + if (one == null || two == null) { + return null; + } + //efficiency not so important because these sets are very small + String[] setsOne = one.getNatureSetIds(); + String[] setsTwo = two.getNatureSetIds(); + for (int iOne = 0; iOne < setsOne.length; iOne++) { + for (int iTwo = 0; iTwo < setsTwo.length; iTwo++) { + if (setsOne[iOne].equals(setsTwo[iTwo])) { + return setsOne[iOne]; + } + } + } + return null; + } + + /** + * Perform depth-first insertion of the given nature ID into the result list. + */ + protected void insert(ArrayList list, Set seen, String id) { + if (seen.contains(id)) + return; + seen.add(id); + //insert prerequisite natures + IProjectNatureDescriptor desc = getNatureDescriptor(id); + if (desc != null) { + String[] prereqs = desc.getRequiredNatureIds(); + for (int i = 0; i < prereqs.length; i++) + insert(list, seen, prereqs[i]); + } + list.add(id); + } + + /* (non-Javadoc) + * Returns true if the given nature is enabled for the given project. + * + * @see IProject#isNatureEnabled(String) + */ + public boolean isNatureEnabled(Project project, String id) { + String[] enabled = getEnabledNatures(project); + for (int i = 0; i < enabled.length; i++) { + if (enabled[i].equals(id)) + return true; + } + return false; + } + + /** + * Only initialize the descriptor cache when we know it is actually needed. + * Running programs may never need to refer to this cache. + */ + private void lazyInitialize() { + if (descriptors != null) + return; + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_NATURES); + IExtension[] extensions = point.getExtensions(); + descriptors = new HashMap<>(extensions.length * 2 + 1); + for (int i = 0, imax = extensions.length; i < imax; i++) { + IProjectNatureDescriptor desc = null; + try { + desc = new ProjectNatureDescriptor(extensions[i]); + } catch (CoreException e) { + Policy.log(e.getStatus()); + } + if (desc != null) + descriptors.put(desc.getNatureId(), desc); + } + //do cycle detection now so it only has to be done once + //cycle detection on a graph subset is a pain + detectCycles(); + } + + @Override + public void shutdown(IProgressMonitor monitor) { + // do nothing + } + + /* (non-Javadoc) + * @see IWorkspace#sortNatureSet(String[]) + */ + public String[] sortNatureSet(String[] natureIds) { + int count = natureIds.length; + if (count == 0) + return natureIds; + ArrayList result = new ArrayList<>(count); + HashSet seen = new HashSet<>(count);//for cycle and duplicate detection + for (int i = 0; i < count; i++) + insert(result, seen, natureIds[i]); + //remove added prerequisites that didn't exist in original list + seen.clear(); + seen.addAll(Arrays.asList(natureIds)); + for (Iterator it = result.iterator(); it.hasNext();) { + Object id = it.next(); + if (!seen.contains(id)) + it.remove(); + } + return result.toArray(new String[result.size()]); + } + + @Override + public void startup(IProgressMonitor monitor) { + ((Workspace) ResourcesPlugin.getWorkspace()).addLifecycleListener(this); + } + + /** + * Validates the given nature additions in the nature set for this + * project. Tolerates existing inconsistencies in the nature set. + * @param newNatures the complete new set of nature IDs for the project, + * including additions + * @param additions the subset of newNatures that represents natures + * being added + * @return An OK status if all additions are valid, and an error status + * if any of the additions introduce new inconsistencies. + */ + protected IStatus validateAdditions(HashSet newNatures, HashSet additions, IProject project) { + Boolean hasLinks = null;//three states: true, false, null (not yet computed) + //perform checks in order from least expensive to most expensive + for (Iterator added = additions.iterator(); added.hasNext();) { + String id = added.next(); + // check for adding a nature that is not available. + IProjectNatureDescriptor desc = getNatureDescriptor(id); + if (desc == null) { + return failure(NLS.bind(Messages.natures_missingNature, id)); + } + // check for adding a nature that creates a circular dependency + if (((ProjectNatureDescriptor) desc).hasCycle) { + return failure(NLS.bind(Messages.natures_hasCycle, id)); + } + // check for adding a nature that has a missing prerequisite. + String[] required = desc.getRequiredNatureIds(); + for (int i = 0; i < required.length; i++) { + if (!newNatures.contains(required[i])) { + return failure(NLS.bind(Messages.natures_missingPrerequisite, id, required[i])); + } + } + // check for adding a nature that creates a duplicated set member. + for (Iterator all = newNatures.iterator(); all.hasNext();) { + String current = all.next(); + if (!current.equals(id)) { + String overlap = hasSetOverlap(desc, getNatureDescriptor(current)); + if (overlap != null) { + return failure(NLS.bind(Messages.natures_multipleSetMembers, overlap)); + } + } + } + //check for adding a nature that has linked resource veto + if (!desc.isLinkingAllowed()) { + if (hasLinks == null) { + hasLinks = hasLinks(project) ? Boolean.TRUE : Boolean.FALSE; + } + if (hasLinks.booleanValue()) + return failure(NLS.bind(Messages.links_vetoNature, project.getName(), id)); + } + } + return Status.OK_STATUS; + } + + /** + * Validates whether a project with the given set of natures should allow + * linked resources. Returns an OK status if linking is allowed, + * otherwise a non-OK status indicating why linking is not allowed. + * Linking is allowed if there is no project nature that explicitly disallows it. + * No validation is done on the nature ids themselves (ids that don't have + * a corresponding nature definition will be ignored). + */ + public IStatus validateLinkCreation(String[] natureIds) { + for (int i = 0; i < natureIds.length; i++) { + IProjectNatureDescriptor desc = getNatureDescriptor(natureIds[i]); + if (desc != null && !desc.isLinkingAllowed()) { + String msg = NLS.bind(Messages.links_natureVeto, desc.getLabel()); + return new ResourceStatus(IResourceStatus.LINKING_NOT_ALLOWED, msg); + } + } + return Status.OK_STATUS; + } + + /** + * Validates the given nature removals in the nature set for this + * project. Tolerates existing inconsistencies in the nature set. + * + * @param newNatures the complete new set of nature IDs for the project, + * excluding deletions + * @param deletions the nature IDs that are being removed from the set. + * @return An OK status if all removals are valid, and a not OK status + * if any of the deletions introduce new inconsistencies. + */ + protected IStatus validateRemovals(HashSet newNatures, HashSet deletions) { + //iterate over new nature set, and ensure that none of their prerequisites are being deleted + for (Iterator it = newNatures.iterator(); it.hasNext();) { + String currentID = it.next(); + IProjectNatureDescriptor desc = getNatureDescriptor(currentID); + if (desc != null) { + String[] required = desc.getRequiredNatureIds(); + for (int i = 0; i < required.length; i++) { + if (deletions.contains(required[i])) { + return failure(NLS.bind(Messages.natures_invalidRemoval, required[i], currentID)); + } + } + } + } + return Status.OK_STATUS; + } + + /* (non-Javadoc) + * @see IWorkspace#validateNatureSet(String[]) + */ + public IStatus validateNatureSet(String[] natureIds) { + int count = natureIds.length; + if (count == 0) + return Status.OK_STATUS; + String msg = Messages.natures_invalidSet; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_NATURE_SET, msg, null); + + //set of the nature ids being validated (String (id)) + HashSet natures = new HashSet<>(count * 2); + //set of nature sets for which a member nature has been found (String (id)) + HashSet sets = new HashSet<>(count); + for (int i = 0; i < count; i++) { + String id = natureIds[i]; + ProjectNatureDescriptor desc = (ProjectNatureDescriptor) getNatureDescriptor(id); + if (desc == null) { + result.add(failure(NLS.bind(Messages.natures_missingNature, id))); + continue; + } + if (desc.hasCycle) + result.add(failure(NLS.bind(Messages.natures_hasCycle, id))); + if (!natures.add(id)) + result.add(failure(NLS.bind(Messages.natures_duplicateNature, id))); + //validate nature set one-of constraint + String[] setIds = desc.getNatureSetIds(); + for (int j = 0; j < setIds.length; j++) { + if (!sets.add(setIds[j])) + result.add(failure(NLS.bind(Messages.natures_multipleSetMembers, setIds[j]))); + } + } + //now walk over the set and ensure all pre-requisite natures are present + for (int i = 0; i < count; i++) { + IProjectNatureDescriptor desc = getNatureDescriptor(natureIds[i]); + if (desc == null) + continue; + String[] required = desc.getRequiredNatureIds(); + for (int j = 0; j < required.length; j++) + if (!natures.contains(required[j])) + result.add(failure(NLS.bind(Messages.natures_missingPrerequisite, natureIds[i], required[j]))); + } + //if there are no problems we must return a status whose code is OK + return result.isOK() ? Status.OK_STATUS : (IStatus) result; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java new file mode 100644 index 0000000000..193ca3490c --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Arrays; +import org.eclipse.core.runtime.Platform; + +/** + * Captures platform specific attributes relevant to the core resources plugin. This + * class is not intended to be instantiated. + */ +public abstract class OS { + private static final String INSTALLED_PLATFORM; + + public static final char[] INVALID_RESOURCE_CHARACTERS; + private static final String[] INVALID_RESOURCE_BASENAMES; + private static final String[] INVALID_RESOURCE_FULLNAMES; + + static { + //find out the OS being used + //setup the invalid names + INSTALLED_PLATFORM = Platform.getOS(); + if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) { + //valid names and characters taken from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp + INVALID_RESOURCE_CHARACTERS = new char[] {'\\', '/', ':', '*', '?', '"', '<', '>', '|'}; + INVALID_RESOURCE_BASENAMES = new String[] {"aux", "com1", "com2", "com3", "com4", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "com5", "com6", "com7", "com8", "com9", "con", "lpt1", "lpt2", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ + "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "nul", "prn"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ + Arrays.sort(INVALID_RESOURCE_BASENAMES); + //CLOCK$ may be used if an extension is provided + INVALID_RESOURCE_FULLNAMES = new String[] {"clock$"}; //$NON-NLS-1$ + } else { + //only front slash and null char are invalid on UNIXes + //taken from http://www.faqs.org/faqs/unix-faq/faq/part2/section-2.html + INVALID_RESOURCE_CHARACTERS = new char[] {'/', '\0',}; + INVALID_RESOURCE_BASENAMES = null; + INVALID_RESOURCE_FULLNAMES = null; + } + } + + /** + * Returns true if the given name is a valid resource name on this operating system, + * and false otherwise. + */ + public static boolean isNameValid(String name) { + //. and .. have special meaning on all platforms + if (name.equals(".") || name.equals("..")) //$NON-NLS-1$ //$NON-NLS-2$ + return false; + if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) { + //empty names are not valid + final int length = name.length(); + if (length == 0) + return false; + final char lastChar = name.charAt(length-1); + // filenames ending in dot are not valid + if (lastChar == '.') + return false; + // file names ending with whitespace are truncated (bug 118997) + if (Character.isWhitespace(lastChar)) + return false; + int dot = name.indexOf('.'); + //on windows, filename suffixes are not relevant to name validity + String basename = dot == -1 ? name : name.substring(0, dot); + if (Arrays.binarySearch(INVALID_RESOURCE_BASENAMES, basename.toLowerCase()) >= 0) + return false; + return Arrays.binarySearch(INVALID_RESOURCE_FULLNAMES, name.toLowerCase()) < 0; + } + return true; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java new file mode 100644 index 0000000000..bdafd8b3f4 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java @@ -0,0 +1,390 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.PathVariableChangeEvent; +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; + +/** + * Core's implementation of IPathVariableManager. + */ +public class PathVariableManager implements IPathVariableManager, IManager { + + static final String VARIABLE_PREFIX = "pathvariable."; //$NON-NLS-1$ + private Set listeners; + private Map> projectListeners; + + private Preferences preferences; + + /** + * Constructor for the class. + */ + public PathVariableManager() { + this.listeners = Collections.synchronizedSet(new HashSet()); + this.projectListeners = Collections.synchronizedMap(new HashMap>()); + this.preferences = ResourcesPlugin.getPlugin().getPluginPreferences(); + } + + /** + * @see org.eclipse.core.resources. + * IPathVariableManager#addChangeListener(IPathVariableChangeListener) + */ + @Override + public void addChangeListener(IPathVariableChangeListener listener) { + listeners.add(listener); + } + + synchronized public void addChangeListener(IPathVariableChangeListener listener, IProject project) { + Collection list = projectListeners.get(project); + if (list == null) { + list = Collections.synchronizedSet(new HashSet()); + projectListeners.put(project, list); + } + list.add(listener); + } + + /** + * Throws a runtime exception if the given name is not valid as a path + * variable name. + */ + private void checkIsValidName(String name) throws CoreException { + IStatus status = validateName(name); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * Throws an exception if the given path is not valid as a path variable + * value. + */ + private void checkIsValidValue(IPath newValue) throws CoreException { + IStatus status = validateValue(newValue); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * Fires a property change event corresponding to a change to the + * current value of the variable with the given name. + * + * @param name the name of the variable, to be used as the variable + * in the event object + * @param value the current value of the path variable or null if + * the variable was deleted + * @param type one of IPathVariableChangeEvent.VARIABLE_CREATED, + * IPathVariableChangeEvent.VARIABLE_CHANGED, or + * IPathVariableChangeEvent.VARIABLE_DELETED + * @see IPathVariableChangeEvent + * @see IPathVariableChangeEvent#VARIABLE_CREATED + * @see IPathVariableChangeEvent#VARIABLE_CHANGED + * @see IPathVariableChangeEvent#VARIABLE_DELETED + */ + private void fireVariableChangeEvent(String name, IPath value, int type) { + fireVariableChangeEvent(this.listeners, name, value, type); + } + + private void fireVariableChangeEvent(Collection list, String name, IPath value, int type) { + if (list.size() == 0) + return; + // use a separate collection to avoid interference of simultaneous additions/removals + Object[] listenerArray = list.toArray(); + final PathVariableChangeEvent pve = new PathVariableChangeEvent(this, name, value, type); + for (int i = 0; i < listenerArray.length; ++i) { + final IPathVariableChangeListener l = (IPathVariableChangeListener) listenerArray[i]; + ISafeRunnable job = new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + // already being logged in SafeRunner#run() + } + + @Override + public void run() throws Exception { + l.pathVariableChanged(pve); + } + }; + SafeRunner.run(job); + } + } + + public void fireVariableChangeEvent(IProject project, String name, IPath value, int type) { + Collection list = projectListeners.get(project); + if (list != null) + fireVariableChangeEvent(list, name, value, type); + } + + /** + * Return a key to use in the Preferences. + */ + private String getKeyForName(String varName) { + return VARIABLE_PREFIX + varName; + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#getPathVariableNames() + */ + @Override + public String[] getPathVariableNames() { + List result = new LinkedList<>(); + String[] names = preferences.propertyNames(); + for (int i = 0; i < names.length; i++) { + if (names[i].startsWith(VARIABLE_PREFIX)) { + String key = names[i].substring(VARIABLE_PREFIX.length()); + // filter out names for preferences which might be valid in the + // preference store but does not have valid path variable names + // and/or values. We can get in this state if the user has + // edited the file on disk or set a preference using the prefix + // reserved to path variables (#VARIABLE_PREFIX). + // TODO: we may want to look at removing these keys from the + // preference store as a garbage collection means + if (validateName(key).isOK() && validateValue(getValue(key)).isOK()) + result.add(key); + } + } + return result.toArray(new String[result.size()]); + } + + /** + * Note that if a user changes the key in the preferences file to be invalid + * and then calls #getValue using that key, they will get the value back for + * that. But then if they try and call #setValue using the same key it will throw + * an exception. We may want to revisit this behaviour in the future. + * + * @see org.eclipse.core.resources.IPathVariableManager#getValue(String) + */ + @Deprecated + @Override + public IPath getValue(String varName) { + String key = getKeyForName(varName); + String value = preferences.getString(key); + return value.length() == 0 ? null : Path.fromPortableString(value); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#isDefined(String) + */ + @Override + public boolean isDefined(String varName) { + return getValue(varName) != null; + } + + /** + * @see org.eclipse.core.resources. + * IPathVariableManager#removeChangeListener(IPathVariableChangeListener) + */ + @Override + public void removeChangeListener(IPathVariableChangeListener listener) { + listeners.remove(listener); + } + + synchronized public void removeChangeListener(IPathVariableChangeListener listener, IProject project) { + Collection list = projectListeners.get(project); + if (list != null) { + list.remove(listener); + if (list.isEmpty()) + projectListeners.remove(project); + } + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#resolvePath(IPath) + */ + @Deprecated + @Override + public IPath resolvePath(IPath path) { + if (path == null || path.segmentCount() == 0 || path.isAbsolute() || path.getDevice() != null) + return path; + IPath value = getValue(path.segment(0)); + return value == null ? path : value.append(path.removeFirstSegments(1)); + } + + @Override + public URI resolveURI(URI uri) { + if (uri == null || uri.isAbsolute()) + return uri; + if (uri.getSchemeSpecificPart() == null) + return uri; + IPath raw = new Path(uri.getSchemeSpecificPart()); + IPath resolved = resolvePath(raw); + return raw == resolved ? uri : URIUtil.toURI(resolved); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#setValue(String, IPath) + */ + @Override + public void setValue(String varName, IPath newValue) throws CoreException { + checkIsValidName(varName); + //convert path value to canonical form + if (newValue != null && newValue.isAbsolute()) + newValue = FileUtil.canonicalPath(newValue); + checkIsValidValue(newValue); + int eventType; + // read previous value and set new value atomically in order to generate the right event + synchronized (this) { + IPath currentValue = getValue(varName); + boolean variableExists = currentValue != null; + if (!variableExists && newValue == null) + return; + if (variableExists && currentValue.equals(newValue)) + return; + if (newValue == null) { + preferences.setToDefault(getKeyForName(varName)); + eventType = IPathVariableChangeEvent.VARIABLE_DELETED; + } else { + preferences.setValue(getKeyForName(varName), newValue.toPortableString()); + eventType = variableExists ? IPathVariableChangeEvent.VARIABLE_CHANGED : IPathVariableChangeEvent.VARIABLE_CREATED; + } + } + // notify listeners from outside the synchronized block to avoid deadlocks + fireVariableChangeEvent(varName, newValue, eventType); + } + + /** + * @see org.eclipse.core.internal.resources.IManager#shutdown(IProgressMonitor) + */ + @Override + public void shutdown(IProgressMonitor monitor) { + // The preferences for this plug-in are saved in the Plugin.shutdown + // method so we don't have to do it here. + } + + /** + * @see org.eclipse.core.internal.resources.IManager#startup(IProgressMonitor) + */ + @Override + public void startup(IProgressMonitor monitor) { + // since we are accessing the preference store directly, we don't + // need to do any setup here. + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#validateName(String) + */ + @Override + public IStatus validateName(String name) { + String message = null; + if (name.length() == 0) { + message = Messages.pathvar_length; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + char first = name.charAt(0); + if (!Character.isLetter(first) && first != '_') { + message = NLS.bind(Messages.pathvar_beginLetter, String.valueOf(first)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + for (int i = 1; i < name.length(); i++) { + char following = name.charAt(i); + if (Character.isWhitespace(following)) + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, Messages.pathvar_whitespace); + if (!Character.isLetter(following) && !Character.isDigit(following) && following != '_') { + message = NLS.bind(Messages.pathvar_invalidChar, String.valueOf(following)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + } + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#validateValue(IPath) + */ + @Override + public IStatus validateValue(IPath value) { + if (value != null && (!value.isValidPath(value.toString()) || !value.isAbsolute())) { + String message = Messages.pathvar_invalidValue; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#convertToRelative(URI, boolean, String) + */ + @Override + public URI convertToRelative(URI path, boolean force, String variableHint) throws CoreException { + return PathVariableUtil.convertToRelative(this, path, null, false, variableHint); + } + + /** + * see IPathVariableManager#getURIValue(String) + */ + @Override + public URI getURIValue(String name) { + IPath path = getValue(name); + if (path != null) + return URIUtil.toURI(path); + return null; + } + + /** + * see IPathVariableManager#setURIValue(String, URI) + */ + @Override + public void setURIValue(String name, URI value) throws CoreException { + setValue(name, (value != null ? URIUtil.toPath(value) : null)); + } + + /** + * @see IPathVariableManager#validateValue(URI) + */ + @Override + public IStatus validateValue(URI path) { + return validateValue(path != null ? URIUtil.toPath(path) : (IPath) null); + } + + public URI resolveURI(URI uri, IResource resource) { + return resolveURI(uri); + } + + public String[] getPathVariableNames(IResource resource) { + return getPathVariableNames(); + } + + @Override + public URI getVariableRelativePathLocation(URI location) { + try { + URI result = convertToRelative(location, false, null); + if (!result.equals(location)) + return result; + } catch (CoreException e) { + // handled by returning null + } + return null; + } + + /** + * @see IPathVariableManager#convertToUserEditableFormat(String, boolean) + */ + @Override + public String convertToUserEditableFormat(String value, boolean locationFormat) { + return PathVariableUtil.convertToUserEditableFormatInternal(value, locationFormat); + } + + @Override + public String convertFromUserEditableFormat(String userFormat, boolean locationFormat) { + return PathVariableUtil.convertFromUserEditableFormatInternal(this, userFormat, locationFormat); + } + + @Override + public boolean isUserDefined(String name) { + return ProjectVariableProviderManager.getDefault().findDescriptor(name) == null; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java new file mode 100644 index 0000000000..a6c288b027 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java @@ -0,0 +1,504 @@ +/******************************************************************************* + * Copyright (c) 2008, 2016 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.util.ArrayList; +import java.util.LinkedList; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.resources.projectvariables.*; +import org.eclipse.core.resources.IPathVariableManager; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.*; + +public class PathVariableUtil { + + static public String getUniqueVariableName(String variable, IResource resource) { + int index = 1; + variable = getValidVariableName(variable); + String destVariable = variable; + + IPathVariableManager pathVariableManager = resource.getPathVariableManager(); + if (destVariable.startsWith(ParentVariableResolver.NAME) || destVariable.startsWith(ProjectLocationVariableResolver.NAME)) + destVariable = "copy_" + destVariable; //$NON-NLS-1$ + + while (pathVariableManager.isDefined(destVariable)) { + destVariable = destVariable + index; + index++; + } + return destVariable; + } + + public static String getValidVariableName(String variable) { + // remove the argument part if the variable is of the form ${VAR-ARG} + int argumentIndex = variable.indexOf('-'); + if (argumentIndex != -1) + variable = variable.substring(0, argumentIndex); + + variable = variable.trim(); + char first = variable.charAt(0); + if (!Character.isLetter(first) && first != '_') { + variable = 'A' + variable; + } + + StringBuffer builder = new StringBuffer(); + for (int i = 0; i < variable.length(); i++) { + char c = variable.charAt(i); + if ((Character.isLetter(c) || Character.isDigit(c) || c == '_') && !Character.isWhitespace(c)) + builder.append(c); + } + variable = builder.toString(); + return variable; + } + + public static IPath convertToPathRelativeMacro(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint) throws CoreException { + return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, true, true); + } + + static public IPath convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint) throws CoreException { + return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, true, false); + } + + static public URI convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint) throws CoreException { + return URIUtil.toURI(convertToRelative(pathVariableManager, URIUtil.toPath(originalPath), resource, force, variableHint, true, false)); + } + + static public URI convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro) throws CoreException { + return URIUtil.toURI(convertToRelative(pathVariableManager, URIUtil.toPath(originalPath), resource, force, variableHint)); + } + + static private IPath convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro) throws CoreException { + if (variableHint != null && pathVariableManager.isDefined(variableHint)) { + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variableHint)); + if (value != null) + return wrapInProperFormat(makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variableHint, generateMacro), generateMacro); + } + IPath path = convertToProperCase(originalPath); + IPath newPath = null; + int maxMatchLength = -1; + String[] existingVariables = pathVariableManager.getPathVariableNames(); + for (int i = 0; i < existingVariables.length; i++) { + String variable = existingVariables[i]; + if (skipWorkspace) { + // Variables relative to the workspace are not portable, and defeat the purpose of having linked resource locations, + // so they should not automatically be created relative to the workspace. + if (variable.equals(WorkspaceLocationVariableResolver.NAME)) + continue; + } + if (variable.equals(WorkspaceParentLocationVariableResolver.NAME)) + continue; + if (variable.equals(ParentVariableResolver.NAME)) + continue; + // find closest path to the original path + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); + if (value != null) { + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + if (value.isPrefixOf(path)) { + int matchLength = value.segmentCount(); + if (matchLength > maxMatchLength) { + maxMatchLength = matchLength; + newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); + } + } + } + } + if (newPath != null) + return wrapInProperFormat(newPath, generateMacro); + + if (force) { + int originalSegmentCount = originalPath.segmentCount(); + for (int j = 0; j <= originalSegmentCount; j++) { + IPath matchingPath = path.removeLastSegments(j); + int minDifference = Integer.MAX_VALUE; + for (int k = 0; k < existingVariables.length; k++) { + String variable = existingVariables[k]; + if (skipWorkspace) { + if (variable.equals(WorkspaceLocationVariableResolver.NAME)) + continue; + } + if (variable.equals(WorkspaceParentLocationVariableResolver.NAME)) + continue; + if (variable.equals(ParentVariableResolver.NAME)) + continue; + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); + if (value != null) { + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + if (matchingPath.isPrefixOf(value)) { + int difference = value.segmentCount() - originalSegmentCount; + if (difference < minDifference) { + minDifference = difference; + newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); + } + } + } + } + if (newPath != null) + return wrapInProperFormat(newPath, generateMacro); + } + if (originalSegmentCount == 0) { + String variable = ProjectLocationVariableResolver.NAME; + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + if (originalPath.isPrefixOf(value)) + newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); + if (newPath != null) + return wrapInProperFormat(newPath, generateMacro); + } + } + + if (skipWorkspace) + return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, false, generateMacro); + return originalPath; + } + + private static IPath wrapInProperFormat(IPath newPath, boolean generateMacro) { + if (generateMacro) + newPath = PathVariableUtil.buildVariableMacro(newPath); + return newPath; + } + + private static IPath makeRelativeToVariable(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean generateMacro) { + IPath path = convertToProperCase(originalPath); + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variableHint)); + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + int valueSegmentCount = value.segmentCount(); + if (value.isPrefixOf(path)) { + // transform "c:/foo/bar" into "FOO/bar" + IPath tmp = Path.fromOSString(variableHint); + for (int j = valueSegmentCount; j < originalPath.segmentCount(); j++) { + tmp = tmp.append(originalPath.segment(j)); + } + return tmp; + } + + if (force) { + if (devicesAreCompatible(path, value)) { + // transform "c:/foo/bar/other_child/file.txt" into "${PARENT-1-BAR_CHILD}/other_child/file.txt" + int matchingFirstSegments = path.matchingFirstSegments(value); + if (matchingFirstSegments >= 0) { + String originalName = buildParentPathVariable(variableHint, valueSegmentCount - matchingFirstSegments, true); + IPath tmp = Path.fromOSString(originalName); + for (int j = matchingFirstSegments; j < originalPath.segmentCount(); j++) { + tmp = tmp.append(originalPath.segment(j)); + } + return tmp; + } + } + } + return originalPath; + } + + private static boolean devicesAreCompatible(IPath path, IPath value) { + return (path.getDevice() != null && value.getDevice() != null) ? (path.getDevice().equals(value.getDevice())) : (path.getDevice() == value.getDevice()); + } + + static private IPath convertToProperCase(IPath path) { + if (Platform.getOS().equals(Platform.OS_WIN32)) + return Path.fromPortableString(path.toPortableString().toLowerCase()); + return path; + } + + static public boolean isParentVariable(String variableString) { + return variableString.startsWith(ParentVariableResolver.NAME + '-'); + } + + // the format is PARENT-COUNT-ARGUMENT + static public int getParentVariableCount(String variableString) { + String items[] = variableString.split("-"); //$NON-NLS-1$ + if (items.length == 3) { + try { + Integer count = Integer.valueOf(items[1]); + return count.intValue(); + } catch (NumberFormatException e) { + // nothing + } + } + return -1; + } + + // the format is PARENT-COUNT-ARGUMENT + static public String getParentVariableArgument(String variableString) { + String items[] = variableString.split("-"); //$NON-NLS-1$ + if (items.length == 3) + return items[2]; + return null; + } + + static public String buildParentPathVariable(String variable, int difference, boolean generateMacro) { + String newString = ParentVariableResolver.NAME + "-" + difference + "-" + variable; //$NON-NLS-1$//$NON-NLS-2$ + + if (!generateMacro) + newString = "${" + newString + "}"; //$NON-NLS-1$//$NON-NLS-2$ + return newString; + } + + public static IPath buildVariableMacro(IPath relativeSrcValue) { + String variable = relativeSrcValue.segment(0); + variable = "${" + variable + "}"; //$NON-NLS-1$//$NON-NLS-2$ + return Path.fromOSString(variable).append(relativeSrcValue.removeFirstSegments(1)); + } + + public static String convertFromUserEditableFormatInternal(IPathVariableManager manager, String userFormat, boolean locationFormat) { + char pathPrefix = 0; + if ((userFormat.length() > 0) && (userFormat.charAt(0) == '/' || userFormat.charAt(0) == '\\')) + pathPrefix = userFormat.charAt(0); + String components[] = splitPathComponents(userFormat); + for (int i = 0; i < components.length; i++) { + if (components[i] == null) + continue; + if (isDotDot(components[i])) { + int parentCount = 1; + components[i] = null; + for (int j = i + 1; j < components.length; j++) { + if (components[j] != null) { + if (isDotDot(components[j])) { + parentCount++; + components[j] = null; + } else + break; + } + } + if (i == 0) // this means the value is implicitly relative to the project location + components[0] = PathVariableUtil.buildParentPathVariable(ProjectLocationVariableResolver.NAME, parentCount, false); + else { + for (int j = i - 1; j >= 0; j--) { + if (parentCount == 0) + break; + if (components[j] == null) + continue; + String variable = extractVariable(components[j]); + + boolean hasVariableWithMacroSyntax = true; + if (variable.length() == 0 && (locationFormat && j == 0)) { + variable = components[j]; + hasVariableWithMacroSyntax = false; + } + + try { + if (variable.length() > 0) { + String prefix = ""; //$NON-NLS-1$ + if (hasVariableWithMacroSyntax) { + int indexOfVariable = components[j].indexOf(variable) - "${".length(); //$NON-NLS-1$ + prefix = components[j].substring(0, indexOfVariable); + String suffix = components[j].substring(indexOfVariable + "${".length() + variable.length() + "}".length()); //$NON-NLS-1$ //$NON-NLS-2$ + if (suffix.length() != 0) { + // Create an intermediate variable, since a syntax of "${VAR}foo/../" + // can't be converted to a "${PARENT-1-VAR}foo" variable. + // So instead, an intermediate variable "VARFOO" will be created of value + // "${VAR}foo", and the string "${PARENT-1-VARFOO}" will be inserted. + String intermediateVariable = PathVariableUtil.getValidVariableName(variable + suffix); + IPath intermediateValue = Path.fromPortableString(components[j]); + int intermediateVariableIndex = 1; + String originalIntermediateVariableName = intermediateVariable; + while (manager.isDefined(intermediateVariable)) { + IPath tmpValue = URIUtil.toPath(manager.getURIValue(intermediateVariable)); + if (tmpValue.equals(intermediateValue)) + break; + intermediateVariable = originalIntermediateVariableName + intermediateVariableIndex; + } + if (!manager.isDefined(intermediateVariable)) + manager.setURIValue(intermediateVariable, URIUtil.toURI(intermediateValue)); + variable = intermediateVariable; + prefix = ""; //$NON-NLS-1$ + } + } + String newVariable = variable; + if (PathVariableUtil.isParentVariable(variable)) { + String argument = PathVariableUtil.getParentVariableArgument(variable); + int count = PathVariableUtil.getParentVariableCount(variable); + if (argument != null && count != -1) + newVariable = PathVariableUtil.buildParentPathVariable(argument, count + parentCount, locationFormat); + else + newVariable = PathVariableUtil.buildParentPathVariable(variable, parentCount, locationFormat); + } else + newVariable = PathVariableUtil.buildParentPathVariable(variable, parentCount, locationFormat); + components[j] = prefix + newVariable; + break; + } + components[j] = null; + parentCount--; + } catch (CoreException e) { + components[j] = null; + parentCount--; + } + } + } + } + } + StringBuffer buffer = new StringBuffer(); + if (pathPrefix != 0) + buffer.append(pathPrefix); + for (int i = 0; i < components.length; i++) { + if (components[i] != null) { + if (i > 0) + buffer.append(java.io.File.separator); + buffer.append(components[i]); + } + } + return buffer.toString(); + } + + private static boolean isDotDot(String component) { + return component.equals(".."); //$NON-NLS-1$ + } + + private static String[] splitPathComponents(String userFormat) { + ArrayList list = new ArrayList<>(); + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < userFormat.length(); i++) { + char c = userFormat.charAt(i); + if (c == '/' || c == '\\') { + if (buffer.length() > 0) + list.add(buffer.toString()); + buffer = new StringBuffer(); + } else + buffer.append(c); + } + if (buffer.length() > 0) + list.add(buffer.toString()); + return list.toArray(new String[0]); + } + + public static String convertToUserEditableFormatInternal(String value, boolean locationFormat) { + StringBuffer buffer = new StringBuffer(); + if (locationFormat) { + IPath path = Path.fromOSString(value); + if (path.isAbsolute()) + return path.toOSString(); + int index = value.indexOf(java.io.File.separator); + String variable = index != -1 ? value.substring(0, index) : value; + convertVariableToUserFormat(buffer, variable, variable, false); + if (index != -1) + buffer.append(value.substring(index)); + } else { + String components[] = splitVariablesAndContent(value); + for (int i = 0; i < components.length; i++) { + String variable = extractVariable(components[i]); + convertVariableToUserFormat(buffer, components[i], variable, true); + } + } + return buffer.toString(); + } + + private static void convertVariableToUserFormat(StringBuffer buffer, String component, String variable, boolean generateMacro) { + if (PathVariableUtil.isParentVariable(variable)) { + String argument = PathVariableUtil.getParentVariableArgument(variable); + int count = PathVariableUtil.getParentVariableCount(variable); + if (argument != null && count != -1) { + buffer.append(generateMacro ? PathVariableUtil.buildVariableMacro(Path.fromOSString(argument)) : Path.fromOSString(argument)); + for (int j = 0; j < count; j++) { + buffer.append(java.io.File.separator + ".."); //$NON-NLS-1$ + } + } else + buffer.append(component); + } else + buffer.append(component); + } + + /* + * Splits a value (returned by this.getValue(variable) in an array of + * string, where the array is divided between the value content and the + * value variables. + * + * For example, if the value is "${ECLIPSE_HOME}/plugins", the value + * returned will be {"${ECLIPSE_HOME}" "/plugins"} + */ + static String[] splitVariablesAndContent(String value) { + LinkedList result = new LinkedList<>(); + while (true) { + // we check if the value contains referenced variables with ${VAR} + int index = value.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = getMatchingBrace(value, index); + if (index > 0) + result.add(value.substring(0, index)); + result.add(value.substring(index, endIndex + 1)); + value = value.substring(endIndex + 1); + } else + break; + } + if (value.length() > 0) + result.add(value); + return result.toArray(new String[0]); + } + + /* + * Splits a value (returned by this.getValue(variable) in an array of + * string of the variables contained in the value. + * + * For example, if the value is "${ECLIPSE_HOME}/plugins", the value + * returned will be {"ECLIPSE_HOME"}. If the value is + * "${ECLIPSE_HOME}/${FOO}/plugins", the value returned will be + * {"ECLIPSE_HOME", "FOO"}. + */ + static String[] splitVariableNames(String value) { + LinkedList result = new LinkedList<>(); + while (true) { + int index = value.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = getMatchingBrace(value, index); + result.add(value.substring(index + 2, endIndex)); + value = value.substring(endIndex + 1); + } else + break; + } + return result.toArray(new String[0]); + } + + /* + * Extracts the variable name from a variable segment. + * + * For example, if the value is "${ECLIPSE_HOME}", the value returned will + * be "ECLIPSE_HOME". If the segment doesn't contain any variable, the value + * returned will be "". + */ + static String extractVariable(String segment) { + int index = segment.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = getMatchingBrace(segment, index); + return segment.substring(index + 2, endIndex); + } + return ""; //$NON-NLS-1$ + } + + // getMatchingBrace("${FOO}/something") returns 5 + // getMatchingBrace("${${OTHER}}/something") returns 10 + // getMatchingBrace("${FOO") returns 5 + static int getMatchingBrace(String value, int index) { + int scope = 0; + for (int i = index + 1; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '}') { + if (scope == 0) + return i; + scope--; + } + if (c == '$') { + if ((i + 1 < value.length()) && (value.charAt(i + 1) == '{')) + scope++; + } + } + return value.length(); + } + + /** + * Returns whether this variable is suited for programmatically determining + * which variable is the most appropriate when creating new linked resources. + * + * @return true if the path variable is preferred. + */ + static public boolean isPreferred(String variableName) { + return !(variableName.equals(WorkspaceLocationVariableResolver.NAME) || variableName.equals(WorkspaceParentLocationVariableResolver.NAME) || variableName.equals(ParentVariableResolver.NAME)); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java new file mode 100644 index 0000000000..b8fa8f03f0 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * 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 java.io.IOException; +import java.net.*; +import org.eclipse.core.internal.boot.PlatformURLConnection; +import org.eclipse.core.internal.boot.PlatformURLHandler; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.osgi.util.NLS; + +/** + * Platform URL support + * platform:/resource// maps to resource in current workspace + */ +public class PlatformURLResourceConnection extends PlatformURLConnection { + + // resource/ protocol + public static final String RESOURCE = "resource"; //$NON-NLS-1$ + public static final String RESOURCE_URL_STRING = PlatformURLHandler.PROTOCOL + PlatformURLHandler.PROTOCOL_SEPARATOR + '/' + RESOURCE + '/'; + private static URL rootURL; + + public PlatformURLResourceConnection(URL url) { + super(url); + } + + @Override + protected boolean allowCaching() { + return false; // don't cache, workspace is local + } + + @Override + protected URL resolve() throws IOException { + String filePath = url.getFile().trim(); + filePath = URLDecoder.decode(filePath, "UTF-8"); //$NON-NLS-1$ + IPath spec = new Path(filePath).makeRelative(); + if (!spec.segment(0).equals(RESOURCE)) + throw new IOException(NLS.bind(Messages.url_badVariant, url)); + int count = spec.segmentCount(); + // if there is only one segment then we are talking about the workspace root. + if (count == 1) + return rootURL; + // if there are two segments then the second is a project name. + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(spec.segment(1)); + if (!project.exists()) { + String message = NLS.bind(Messages.url_couldNotResolve_projectDoesNotExist, project.getName(), url.toExternalForm()); + throw new IOException(message); + } + + IResource resource = null; + IPath result = null; + + if (count == 2) { + resource = project; + } else { + spec = spec.removeFirstSegments(2); + resource = project.getFile(spec); + } + + result = resource.getLocation(); + + if (result == null) { + URI uri = resource.getLocationURI(); + if (uri != null) { + try { + URL url2 = uri.toURL(); + if (url2.getProtocol().equals("file")) //$NON-NLS-1$ + return url2; + } catch (MalformedURLException e) { + String message = NLS.bind(Messages.url_couldNotResolve_URLProtocolHandlerCanNotResolveURL, uri.toString(), url.toExternalForm()); + throw new IOException(message); + } + } + String message = NLS.bind(Messages.url_couldNotResolve_resourceLocationCanNotBeDetermined, resource.getFullPath(), url.toExternalForm()); + throw new IOException(message); + } + return new URL("file", "", result.toString()); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * This method is called during resource plugin startup() initialization. + * + * @param root URL to the root of the current workspace. + */ + public static void startup(IPath root) { + // register connection type for platform:/resource/ handling + if (rootURL != null) + return; + try { + rootURL = root.toFile().toURL(); + } catch (MalformedURLException e) { + // should never happen but if it does, the resource URL cannot be supported. + return; + } + PlatformURLHandler.register(RESOURCE, PlatformURLResourceConnection.class); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java new file mode 100644 index 0000000000..3ae12e17dc --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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 Corporation - initial API and implementation + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.preferences.*; + +/** + * @since 3.1 + */ +public class PreferenceInitializer extends AbstractPreferenceInitializer { + + // internal preference keys + public static final String PREF_OPERATIONS_PER_SNAPSHOT = "snapshots.operations"; //$NON-NLS-1$ + public static final String PREF_DELTA_EXPIRATION = "delta.expiration"; //$NON-NLS-1$ + + // DEFAULTS + public static final boolean PREF_AUTO_REFRESH_DEFAULT = false; + public static final boolean PREF_LIGHTWEIGHT_AUTO_REFRESH_DEFAULT = false; + public static final boolean PREF_DISABLE_LINKING_DEFAULT = false; + public static final String PREF_ENCODING_DEFAULT = ""; //$NON-NLS-1$ + public static final boolean PREF_AUTO_BUILDING_DEFAULT = true; + public static final String PREF_BUILD_ORDER_DEFAULT = ""; //$NON-NLS-1$ + public static final int PREF_MAX_BUILD_ITERATIONS_DEFAULT = 10; + public static final boolean PREF_DEFAULT_BUILD_ORDER_DEFAULT = true; + public final static long PREF_SNAPSHOT_INTERVAL_DEFAULT = 5 * 60 * 1000l; // 5 min + public static final int PREF_OPERATIONS_PER_SNAPSHOT_DEFAULT = 100; + public static final boolean PREF_APPLY_FILE_STATE_POLICY_DEFAULT = true; + public static final long PREF_FILE_STATE_LONGEVITY_DEFAULT = 7 * 24 * 3600 * 1000l; // 7 days + public static final long PREF_MAX_FILE_STATE_SIZE_DEFAULT = 1024 * 1024l; // 1 MB + public static final int PREF_MAX_FILE_STATES_DEFAULT = 50; + public static final long PREF_DELTA_EXPIRATION_DEFAULT = 30 * 24 * 3600 * 1000l; // 30 days + + public PreferenceInitializer() { + super(); + } + + @Override + public void initializeDefaultPreferences() { + IEclipsePreferences node = DefaultScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES); + // auto-refresh default + node.putBoolean(ResourcesPlugin.PREF_AUTO_REFRESH, PREF_AUTO_REFRESH_DEFAULT); + node.putBoolean(ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, PREF_LIGHTWEIGHT_AUTO_REFRESH_DEFAULT); + + // linked resources default + node.putBoolean(ResourcesPlugin.PREF_DISABLE_LINKING, PREF_DISABLE_LINKING_DEFAULT); + + // build manager defaults + node.putBoolean(ResourcesPlugin.PREF_AUTO_BUILDING, PREF_AUTO_BUILDING_DEFAULT); + node.put(ResourcesPlugin.PREF_BUILD_ORDER, PREF_BUILD_ORDER_DEFAULT); + node.putInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS, PREF_MAX_BUILD_ITERATIONS_DEFAULT); + node.putBoolean(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER, PREF_DEFAULT_BUILD_ORDER_DEFAULT); + + // history store defaults + node.putBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY, PREF_APPLY_FILE_STATE_POLICY_DEFAULT); + node.putLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, PREF_FILE_STATE_LONGEVITY_DEFAULT); + node.putLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, PREF_MAX_FILE_STATE_SIZE_DEFAULT); + node.putInt(ResourcesPlugin.PREF_MAX_FILE_STATES, PREF_MAX_FILE_STATES_DEFAULT); + + // save manager defaults + node.putLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL, PREF_SNAPSHOT_INTERVAL_DEFAULT); + node.putInt(PREF_OPERATIONS_PER_SNAPSHOT, PREF_OPERATIONS_PER_SNAPSHOT_DEFAULT); + node.putLong(PREF_DELTA_EXPIRATION, PREF_DELTA_EXPIRATION_DEFAULT); + + // encoding defaults + node.put(ResourcesPlugin.PREF_ENCODING, PREF_ENCODING_DEFAULT); + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java new file mode 100644 index 0000000000..75bcb0b167 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java @@ -0,0 +1,1370 @@ +/******************************************************************************* + * 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] Project Path Variable Support + * Anton Leherbauer (Wind River) - [198591] Allow Builder to specify scheduling rule + * Francis Lynch (Wind River) - [301563] Save and load tree snapshots + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * Broadcom Corporation - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.internal.events.LifecycleEvent; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IMoveDeleteHook; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentTypeMatcher; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.osgi.util.NLS; + +public class Project extends Container implements IProject { + /** + * Option constant (value 2) indicating that the snapshot location shall be + * persisted with the project for autoloading the snapshot when the project + * is imported in another workspace. + *

          + * EXPERIMENTAL. This constant has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. Please do not use this API without + * consulting with the Platform Core team. + *

          + * @see #saveSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public static final int SNAPSHOT_SET_AUTOLOAD = 2; + + protected Project(IPath path, Workspace container) { + super(path, container); + } + + protected void assertCreateRequirements(IProjectDescription description) throws CoreException { + checkDoesNotExist(); + checkDescription(this, description, false); + URI location = description.getLocationURI(); + if (location != null) + return; + //if the project is in the default location, need to check for collision with existing folder of different case + if (!Workspace.caseSensitive) { + IFileStore store = getStore(); + IFileInfo localInfo = store.fetchInfo(); + if (localInfo.exists()) { + 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); + } + } + } + } + + /* + * If the creation boolean is true then this method is being called on project creation. + * Otherwise it is being called via #setDescription. The difference is that we don't allow + * some description fields to change value after project creation. (e.g. project location) + */ + protected MultiStatus basicSetDescription(ProjectDescription description, int updateFlags) { + String message = Messages.resources_projectDesc; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_WRITE_METADATA, message, null); + ProjectDescription current = internalGetDescription(); + current.setComment(description.getComment()); + current.setSnapshotLocationURI(description.getSnapshotLocationURI()); + + // set the build order before setting the references or the natures + current.setBuildSpec(description.getBuildSpec(true)); + + // set the references before the natures + boolean flushOrder = false; + IProject[] oldReferences = current.getReferencedProjects(); + IProject[] newReferences = description.getReferencedProjects(); + if (!Arrays.equals(oldReferences, newReferences)) { + current.setReferencedProjects(newReferences); + flushOrder = true; + } + // Update the dynamic state + flushOrder |= current.updateDynamicState(description); + + if (flushOrder) + workspace.flushBuildOrder(); + + // the natures last as this may cause recursive calls to setDescription. + if ((updateFlags & IResource.AVOID_NATURE_CONFIG) == 0) + workspace.getNatureManager().configureNatures(this, current, description, result); + else + current.setNatureIds(description.getNatureIds(false)); + return result; + } + + @Override + public void build(int trigger, IProgressMonitor monitor) throws CoreException { + if (!isAccessible()) + return; + internalBuild(getActiveBuildConfig(), trigger, null, null, monitor); + } + + @Override + public void build(int trigger, String builderName, Map args, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(builderName); + if (!isAccessible()) + return; + internalBuild(getActiveBuildConfig(), trigger, builderName, args, monitor); + } + + @Override + public void build(IBuildConfiguration config, int trigger, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(config); + // If project isn't accessible, or doesn't contain the build configuration, nothing to do. + if (!isAccessible() || !hasBuildConfig(config.getName())) + return; + internalBuild(config, trigger, null, null, monitor); + } + + /** + * Checks that this resource is accessible. Typically this means that it + * exists. In the case of projects, they must also be open. + * If phantom is true, phantom resources are considered. + * + * @exception CoreException if this resource is not accessible + */ + @Override + public void checkAccessible(int flags) throws CoreException { + super.checkAccessible(flags); + if (!isOpen(flags)) { + String message = NLS.bind(Messages.resources_mustBeOpen, getFullPath()); + throw new ResourceException(IResourceStatus.PROJECT_NOT_OPEN, getFullPath(), message, null); + } + } + + /** + * Checks validity of the given project description. + */ + protected void checkDescription(IProject project, IProjectDescription desc, boolean moving) throws CoreException { + URI location = desc.getLocationURI(); + String message = Messages.resources_invalidProjDesc; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_VALUE, message, null); + status.merge(workspace.validateName(desc.getName(), IResource.PROJECT)); + if (moving) { + // if we got here from a move call then we should check the location in the description since + // its possible that we want to do a rename without moving the contents. (and we shouldn't + // throw an Overlapping mapping exception in this case) So if the source description's location + // is null (we are using the default) or if the locations aren't equal, then validate the location + // of the new description. Otherwise both locations aren't null and they are equal so ignore validation. + URI sourceLocation = internalGetDescription().getLocationURI(); + if (sourceLocation == null || !sourceLocation.equals(location)) + status.merge(workspace.validateProjectLocationURI(project, location)); + } else + // otherwise continue on like before + status.merge(workspace.validateProjectLocationURI(project, location)); + if (!status.isOK()) + throw new ResourceException(status); + } + + @Override + public void close(IProgressMonitor monitor) throws CoreException { + String msg = NLS.bind(Messages.resources_closing_1, getName()); + SubMonitor subMonitor = SubMonitor.convert(monitor, msg, 100); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, subMonitor.newChild(1)); + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkExists(flags, true); + subMonitor.subTask(msg); + if (!isOpen(flags)) + return; + // Signal that this resource is about to be closed. Do this at the very + // beginning so that infrastructure pieces have a chance to do clean up + // while the resources still exist. + workspace.beginOperation(true); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, this)); + // flush the build order early in case there is a problem + workspace.flushBuildOrder(); + IProgressMonitor sub = subMonitor.newChild(49, SubMonitor.SUPPRESS_SUBTASK); + IStatus saveStatus = workspace.getSaveManager().save(ISaveContext.PROJECT_SAVE, this, sub); + internalClose(subMonitor.newChild(49)); + if (saveStatus != null && !saveStatus.isOK()) + throw new ResourceException(saveStatus); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, subMonitor.newChild(1)); + } + } + + @Override + public void copy(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + // FIXME - the logic here for copying projects needs to be moved to Resource.copy + // so that IResource.copy(IPath,int,IProgressMonitor) works properly for + // projects and honours all update flags + monitor = Policy.monitorFor(monitor); + if (destination.segmentCount() == 1) { + // copy project to project + String projectName = destination.segment(0); + IProjectDescription desc = getDescription(); + desc.setName(projectName); + desc.setLocation(null); + ((ProjectDescription) desc).setSnapshotLocationURI(null); + internalCopy(desc, updateFlags, monitor); + } else { + // will fail since we're trying to copy a project to a non-project + checkCopyRequirements(destination, IResource.PROJECT, updateFlags); + } + } + + @Override + public void copy(IProjectDescription destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + // FIXME - the logic here for copying projects needs to be moved to Resource.copy + // so that IResource.copy(IProjectDescription,int,IProgressMonitor) works properly for + // projects and honours all update flags + Assert.isNotNull(destination); + internalCopy(destination, updateFlags, monitor); + } + + protected void copyMetaArea(IProject source, IProject destination, IProgressMonitor monitor) throws CoreException { + IFileStore oldMetaArea = EFS.getFileSystem(EFS.SCHEME_FILE).getStore(workspace.getMetaArea().locationFor(source)); + IFileStore newMetaArea = EFS.getFileSystem(EFS.SCHEME_FILE).getStore(workspace.getMetaArea().locationFor(destination)); + oldMetaArea.copy(newMetaArea, EFS.NONE, monitor); + } + + @Override + public void create(IProgressMonitor monitor) throws CoreException { + create(null, monitor); + } + + @Override + public void create(IProjectDescription description, IProgressMonitor monitor) throws CoreException { + create(description, IResource.NONE, monitor); + } + + @Override + public void create(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask(Messages.resources_create, Policy.totalWork); + checkValidPath(path, PROJECT, false); + final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); + try { + workspace.prepareOperation(rule, monitor); + if (description == null) { + description = new ProjectDescription(); + description.setName(getName()); + } + assertCreateRequirements(description); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CREATE, this)); + workspace.beginOperation(true); + workspace.createResource(this, updateFlags); + workspace.getMetaArea().create(this); + ProjectInfo info = (ProjectInfo) getResourceInfo(false, true); + + // setup description to obtain project location + ProjectDescription desc = (ProjectDescription) ((ProjectDescription) description).clone(); + desc.setLocationURI(FileUtil.canonicalURI(description.getLocationURI())); + desc.setName(getName()); + internalSetDescription(desc, false); + // see if there potentially are already contents on disk + final boolean hasSavedDescription = getLocalManager().hasSavedDescription(this); + boolean hasContent = hasSavedDescription; + //if there is no project description, there might still be content on disk + if (!hasSavedDescription) + hasContent = getLocalManager().hasSavedContent(this); + try { + // look for a description on disk + if (hasSavedDescription) { + updateDescription(); + //make sure the .location file is written + workspace.getMetaArea().writePrivateDescription(this); + } else { + //write out the project + writeDescription(IResource.FORCE); + } + } catch (CoreException e) { + workspace.deleteResource(this); + throw e; + } + // inaccessible projects have a null modification stamp. + // set this after setting the description as #setDescription + // updates the stamp + info.clearModificationStamp(); + //if a project already had content on disk, mark the project as having unknown children + if (hasContent) + info.set(ICoreConstants.M_CHILDREN_UNKNOWN); + workspace.getSaveManager().requestSnapshot(); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT; + delete(updateFlags, monitor); + } + + @Override + public void delete(boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + delete(updateFlags, monitor); + } + + @Override + public void deleteResource(boolean convertToPhantom, MultiStatus status) throws CoreException { + super.deleteResource(convertToPhantom, status); + // Clear the history store. + clearHistory(null); + // Delete the project metadata. + workspace.getMetaArea().delete(this); + } + + @Override + protected void fixupAfterMoveSource() throws CoreException { + workspace.deleteResource(this); + // check if we deleted a preferences file + ProjectPreferences.deleted(this); + } + + @Override + public IBuildConfiguration getActiveBuildConfig() throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + return internalGetActiveBuildConfig(); + } + + @Override + public IBuildConfiguration getBuildConfig(String configName) throws CoreException { + if (configName == null) + return getActiveBuildConfig(); + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + IBuildConfiguration[] configs = internalGetBuildConfigs(false); + for (int i = 0; i < configs.length; i++) { + if (configs[i].getName().equals(configName)) { + return configs[i]; + } + } + throw new ResourceException(IResourceStatus.BUILD_CONFIGURATION_NOT_FOUND, getFullPath(), null, null); + } + + @Override + public IBuildConfiguration[] getBuildConfigs() throws CoreException { + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + return internalGetBuildConfigs(true); + } + + @Override + public IContentTypeMatcher getContentTypeMatcher() throws CoreException { + return workspace.getContentDescriptionManager().getContentTypeMatcher(this); + } + + @Override + public String getDefaultCharset(boolean checkImplicit) { + // non-existing resources default to parent's charset + if (!exists()) + return checkImplicit ? ResourcesPlugin.getEncoding() : null; + return workspace.getCharsetManager().getCharsetFor(getFullPath(), checkImplicit); + } + + @Override + public IProjectDescription getDescription() throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + ProjectDescription description = ((ProjectInfo) info).getDescription(); + //if the project is currently in the middle of being created, the description might not be available yet + if (description == null) + checkAccessible(NULL_FLAG); + return (IProjectDescription) description.clone(); + } + + @Override + public IProjectNature getNature(String natureID) throws CoreException { + // Has it already been initialized? + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + IProjectNature nature = info.getNature(natureID); + if (nature == null) { + // Not initialized yet. Does this project have the nature? + if (!hasNature(natureID)) + return null; + nature = workspace.getNatureManager().createNature(this, natureID); + info.setNature(natureID, nature); + } + return nature; + } + + @Override + public IContainer getParent() { + return workspace.getRoot(); + } + + @Override + @Deprecated + public IPath getPluginWorkingLocation(IPluginDescriptor plugin) { + if (plugin == null) + return null; + return getWorkingLocation(plugin.getUniqueIdentifier()); + } + + @Override + public IProject getProject() { + return this; + } + + @Override + public IPath getProjectRelativePath() { + return Path.EMPTY; + } + + @Override + public IPath getRawLocation() { + ProjectDescription description = internalGetDescription(); + return description == null ? null : description.getLocation(); + } + + @Override + public URI getRawLocationURI() { + ProjectDescription description = internalGetDescription(); + return description == null ? null : description.getLocationURI(); + } + + @Override + public IBuildConfiguration[] getReferencedBuildConfigs(String configName, boolean includeMissing) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + ProjectDescription description = ((ProjectInfo) info).getDescription(); + //if the project is currently in the middle of being created, the description might not be available yet + if (description == null) + checkAccessible(NULL_FLAG); + if (!hasBuildConfig(configName)) + throw new ResourceException(IResourceStatus.BUILD_CONFIGURATION_NOT_FOUND, getFullPath(), null, null); + return internalGetReferencedBuildConfigs(configName, includeMissing); + } + + @Override + public IProject[] getReferencedProjects() throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + ProjectDescription description = ((ProjectInfo) info).getDescription(); + //if the project is currently in the middle of being created, the description might not be available yet + if (description == null) + checkAccessible(NULL_FLAG); + return description.getAllReferences(true); + } + + @Override + public IProject[] getReferencingProjects() { + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List result = new ArrayList<>(projects.length); + for (int i = 0; i < projects.length; i++) { + Project project = (Project) projects[i]; + if (!project.isAccessible()) + continue; + ProjectDescription description = project.internalGetDescription(); + if (description == null) + continue; + IProject[] references = description.getAllReferences(false); + for (int j = 0; j < references.length; j++) + if (references[j].equals(this)) { + result.add(projects[i]); + break; + } + } + return result.toArray(new IProject[result.size()]); + } + + @Override + public int getType() { + return PROJECT; + } + + @Override + public IPath getWorkingLocation(String id) { + if (id == null || !exists()) + return null; + IPath result = workspace.getMetaArea().getWorkingLocation(this, id); + result.toFile().mkdirs(); + return result; + } + + @Override + public boolean hasBuildConfig(String configName) throws CoreException { + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + return internalHasBuildConfig(configName); + } + + @Override + public boolean hasNature(String natureID) throws CoreException { + checkAccessible(getFlags(getResourceInfo(false, false))); + // use #internal method to avoid copy but still throw an + // exception if the resource doesn't exist. + IProjectDescription desc = internalGetDescription(); + if (desc == null) + checkAccessible(NULL_FLAG); + return desc.hasNature(natureID); + } + + /** + * Implements all build methods on IProject. + */ + protected void internalBuild(final IBuildConfiguration config, final int trigger, final String builderName, final Map args, IProgressMonitor monitor) throws CoreException { + workspace.run(new ICoreRunnable() { + @Override + public void run(IProgressMonitor innerMonitor) throws CoreException { + innerMonitor = Policy.monitorFor(innerMonitor); + final ISchedulingRule rule = workspace.getRoot(); + try { + innerMonitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + try { + workspace.prepareOperation(rule, innerMonitor); + if (!shouldBuild()) + return; + workspace.beginOperation(true); + workspace.aboutToBuild(Project.this, trigger); + } finally { + workspace.endOperation(rule, false, innerMonitor); + } + final ISchedulingRule buildRule = workspace.getBuildManager().getRule(config, trigger, builderName, args); + try { + IStatus result; + workspace.prepareOperation(buildRule, innerMonitor); + //don't open the tree eagerly because it will be wasted if no build occurs + workspace.beginOperation(false); + result = workspace.getBuildManager().build(config, trigger, builderName, args, Policy.subMonitorFor(innerMonitor, Policy.opWork)); + if (!result.isOK()) + throw new ResourceException(result); + } finally { + workspace.endOperation(buildRule, false, innerMonitor); + try { + workspace.prepareOperation(rule, innerMonitor); + //don't open the tree eagerly because it will be wasted if no change occurs + workspace.beginOperation(false); + workspace.broadcastBuildEvent(Project.this, IResourceChangeEvent.POST_BUILD, trigger); + //building may close the tree, so open it + if (workspace.getElementTree().isImmutable()) + workspace.newWorkingTree(); + } finally { + workspace.endOperation(rule, false, Policy.subMonitorFor(innerMonitor, Policy.endOpWork)); + } + } + } finally { + innerMonitor.done(); + } + } + + /** + * Returns whether this project should be built for a given trigger. + * @return true if the build should proceed, and false otherwise. + */ + private boolean shouldBuild() { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (!exists(flags, true) || !isOpen(flags)) + return false; + return true; + } + + }, null, IWorkspace.AVOID_UPDATE, monitor); + } + + /** + * Closes the project. This is called during restore when there is a failure + * to read the project description. Since it is called during workspace restore, + * it cannot start any operations. + */ + protected void internalClose(IProgressMonitor monitor) throws CoreException { + SubMonitor subMonitor = SubMonitor.convert(monitor, 2); + workspace.flushBuildOrder(); + getMarkerManager().removeMarkers(this, IResource.DEPTH_INFINITE); + subMonitor.worked(1); + // remove each member from the resource tree. + // DO NOT use resource.delete() as this will delete it from disk as well. + IResource[] members = members(IContainer.INCLUDE_PHANTOMS | IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + subMonitor.setWorkRemaining(members.length); + for (int i = 0; i < members.length; i++) { + Resource member = (Resource) members[i]; + workspace.deleteResource(member); + subMonitor.worked(1); + } + // finally mark the project as closed. + ResourceInfo info = getResourceInfo(false, true); + info.clear(M_OPEN); + info.clearSessionProperties(); + info.clearModificationStamp(); + info.setSyncInfo(null); + } + + protected void internalCopy(IProjectDescription destDesc, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_copying, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + String destName = destDesc.getName(); + IPath destPath = new Path(destName).makeAbsolute(); + Project destination = (Project) workspace.getRoot().getProject(destName); + final ISchedulingRule rule = workspace.getRuleFactory().copyRule(this, destination); + try { + workspace.prepareOperation(rule, monitor); + // The following assert method throws CoreExceptions as stated in the IProject.copy API + // and assert for programming errors. See checkCopyRequirements for more information. + assertCopyRequirements(destPath, IResource.PROJECT, updateFlags); + checkDescription(destination, destDesc, false); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_COPY, this, destination, updateFlags)); + + workspace.beginOperation(true); + getLocalManager().refresh(this, DEPTH_INFINITE, true, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + // close the property store so incorrect info is not copied to the destination + getPropertyManager().closePropertyStore(this); + getLocalManager().getHistoryStore().closeHistoryStore(this); + + // copy the meta area for the project + copyMetaArea(this, destination, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + + // copy just the project and not its children yet (tree node, properties) + internalCopyProjectOnly(destination, destDesc, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + + // set the description + destination.internalSetDescription(destDesc, false); + + //create the directory for the new project + destination.getStore().mkdir(EFS.NONE, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + + // call super.copy for each child (excluding project description file) + //make it a best effort copy + message = Messages.resources_copyProblem; + MultiStatus problems = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + + IResource[] children = members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + final int childCount = children.length; + final int childWork = childCount > 1 ? Policy.opWork * 50 / 100 / (childCount - 1) : 0; + for (int i = 0; i < childCount; i++) { + IResource child = children[i]; + if (!isProjectDescriptionFile(child)) { + try { + child.copy(destPath.append(child.getName()), updateFlags, Policy.subMonitorFor(monitor, childWork)); + } catch (CoreException e) { + problems.merge(e.getStatus()); + } + } + } + + // write out the new project description to the meta area + try { + destination.writeDescription(IResource.FORCE); + } catch (CoreException e) { + try { + destination.delete((updateFlags & IResource.FORCE) != 0, null); + } catch (CoreException e2) { + // ignore and rethrow the exception that got us here + } + throw e; + } + monitor.worked(Policy.opWork * 5 / 100); + + // refresh local + monitor.subTask(Messages.resources_updating); + getLocalManager().refresh(destination, DEPTH_INFINITE, true, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + if (!problems.isOK()) + throw new ResourceException(problems); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + /* + * Copies just the project and no children. Does NOT copy the meta area. + */ + protected void internalCopyProjectOnly(IResource destination, IProjectDescription destDesc, IProgressMonitor monitor) throws CoreException { + // close the property store so bogus values aren't copied to the destination + getPropertyManager().closePropertyStore(this); + getLocalManager().getHistoryStore().closeHistoryStore(this); + // copy the tree and properties + workspace.copyTree(this, destination.getFullPath(), IResource.DEPTH_ZERO, IResource.NONE, false); + getPropertyManager().copy(this, destination, IResource.DEPTH_ZERO); + + ProjectInfo info = (ProjectInfo) ((Resource) destination).getResourceInfo(false, true); + + //copy the hidden metadata that we store in the project description + ProjectDescription projectDesc = (ProjectDescription) destDesc; + ProjectDescription internalDesc = ((Project) destination.getProject()).internalGetDescription(); + projectDesc.setLinkDescriptions(internalDesc.getLinks()); + projectDesc.setFilterDescriptions(internalDesc.getFilters()); + projectDesc.setVariableDescriptions(internalDesc.getVariables()); + + //clear properties, markers, and description for the new project, because they shouldn't be copied. + info.description = null; + info.natures = null; + info.setMarkers(null); + info.clearSessionProperties(); + } + + /** + * Like {@link #getActiveBuildConfig()} but doesn't check accessibility. + * Project must be accessible. + * @see #getActiveBuildConfig() + */ + IBuildConfiguration internalGetActiveBuildConfig() { + String configName = internalGetDescription().activeConfiguration; + try { + if (configName != null) + return getBuildConfig(configName); + } catch (CoreException e) { + // Build configuration doesn't exist; treat the first as active. + } + return internalGetBuildConfigs(false)[0]; + } + + /** + * @return IBuildConfiguration[] always contains at least one build configuration + */ + public IBuildConfiguration[] internalGetBuildConfigs(boolean makeCopy) { + ProjectDescription desc = internalGetDescription(); + if (desc == null) + return new IBuildConfiguration[] {new BuildConfiguration(this, IBuildConfiguration.DEFAULT_CONFIG_NAME)}; + return desc.getBuildConfigs(this, makeCopy); + } + + /** + * This is an internal helper method. This implementation is different from the API + * method getDescription(). This one does not check the project accessibility. It exists + * in order to prevent "chicken and egg" problems in places like the project creation. + * It may return null. + */ + public ProjectDescription internalGetDescription() { + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + if (info == null) + return null; + return info.getDescription(); + } + + /** + * Returns the IBuildConfigurations referenced by the passed in build configuration + * @param configName to find references for + * @return IBuildConfiguration[] of referenced configurations; never null. + */ + public IBuildConfiguration[] internalGetReferencedBuildConfigs(String configName, boolean includeMissing) { + ProjectDescription description = internalGetDescription(); + IBuildConfiguration[] refs = description.getAllBuildConfigReferences(configName, false); + Collection configs = new LinkedHashSet<>(refs.length); + for (int i = 0; i < refs.length; i++) { + try { + configs.add((((BuildConfiguration) refs[i]).getBuildConfig())); + } catch (CoreException e) { + // The project isn't accessible, or the build configuration doesn't exist + // on the project. If requested return the full set of build references which may + // be useful to API consumers + if (includeMissing) + configs.add(refs[i]); + } + } + return configs.toArray(new IBuildConfiguration[configs.size()]); + } + + boolean internalHasBuildConfig(String configName) { + return internalGetDescription().hasBuildConfig(configName); + } + + /** + * Sets this project's description to the given value. This is the body of the + * corresponding API method but is needed separately since it is used + * during workspace restore (i.e., when you cannot do an operation) + */ + void internalSetDescription(IProjectDescription value, boolean incrementContentId) { + // Project has been added / removed. Build order is out-of-step + workspace.flushBuildOrder(); + + ProjectInfo info = (ProjectInfo) getResourceInfo(false, true); + info.setDescription((ProjectDescription) value); + getLocalManager().setLocation(this, info, value.getLocationURI()); + if (incrementContentId) { + info.incrementContentId(); + //if the project is not accessible, stamp will be null and should remain null + if (info.getModificationStamp() != NULL_STAMP) + workspace.updateModificationStamp(info); + } + } + + @Override + public void internalSetLocal(boolean flag, int depth) throws CoreException { + // do nothing for projects, but call for its children + if (depth == IResource.DEPTH_ZERO) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.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++) + ((Resource) children[i]).internalSetLocal(flag, depth); + } + + @Override + public boolean isAccessible() { + return isOpen(); + } + + @Override + public boolean isDerived(int options) { + //projects are never derived + return false; + } + + @Override + public boolean isLinked(int options) { + return false;//projects are never linked + } + + @Override + public boolean isVirtual() { + return false; // projects are never virtual + } + + @Override + public boolean isTeamPrivateMember(int options) { + return false;//projects are never team private members + } + + @Deprecated + @Override + public boolean isLocal(int depth) { + // the flags parameter is ignored for projects so pass anything + return isLocal(-1, depth); + } + + @Deprecated + @Override + public boolean isLocal(int flags, int depth) { + // don't check the flags....projects are always local + 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 boolean isNatureEnabled(String natureId) throws CoreException { + checkAccessible(getFlags(getResourceInfo(false, false))); + return workspace.getNatureManager().isNatureEnabled(this, natureId); + } + + @Override + public boolean isOpen() { + ResourceInfo info = getResourceInfo(false, false); + return isOpen(getFlags(info)); + } + + public boolean isOpen(int flags) { + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_OPEN); + } + + /** + * Returns true if this resource represents the project description file, and + * false otherwise. + */ + protected boolean isProjectDescriptionFile(IResource resource) { + return resource.getType() == IResource.FILE && resource.getFullPath().segmentCount() == 2 && resource.getName().equals(IProjectDescription.DESCRIPTION_FILE_NAME); + } + + @Override + public void loadSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + // load a snapshot of refresh information when project is not opened + if (isOpen()) { + String message = Messages.resources_projectMustNotBeOpen; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, null); + throw new CoreException(status); + } + internalLoadSnapshot(options, snapshotLocation, monitor); + } + + /** + * Loads a snapshot of project meta-data from the given location URI. + * Like {@link IProject#loadSnapshot(int, URI, IProgressMonitor)} but can be + * called when the project is open. + * + * @see IProject#saveSnapshot(int, URI, IProgressMonitor) + */ + private void internalLoadSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + if ((options & SNAPSHOT_TREE) != 0) { + // ensure that path variables are resolved: only ws accessible while project is closed + snapshotLocation = workspace.getPathVariableManager().resolveURI(snapshotLocation); + if (!snapshotLocation.isAbsolute()) { + String message = NLS.bind(Messages.projRead_badSnapshotLocation, snapshotLocation.toString()); + throw new CoreException(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, message, null)); + } + // copy the snapshot from the URI into the project metadata + IPath snapshotPath = workspace.getMetaArea().getRefreshLocationFor(this); + IFileStore snapshotFileStore = EFS.getStore(org.eclipse.core.filesystem.URIUtil.toURI(snapshotPath)); + EFS.getStore(snapshotLocation).copy(snapshotFileStore, EFS.OVERWRITE, monitor); + } + } + + @Override + public void move(IProjectDescription destination, boolean force, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(destination); + move(destination, force ? IResource.FORCE : IResource.NONE, monitor); + } + + @Override + public void move(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(description); + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_moving, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + IProject destination = workspace.getRoot().getProject(description.getName()); + final ISchedulingRule rule = workspace.getRuleFactory().moveRule(this, destination); + try { + workspace.prepareOperation(rule, monitor); + // The following assert method throws CoreExceptions as stated in the IResource.move API + // and assert for programming errors. See checkMoveRequirements for more information. + if (!getName().equals(description.getName())) { + IPath destPath = Path.ROOT.append(description.getName()); + assertMoveRequirements(destPath, IResource.PROJECT, updateFlags); + } + checkDescription(destination, description, true); + workspace.beginOperation(true); + message = Messages.resources_moveProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, null); + WorkManager workManager = workspace.getWorkManager(); + ResourceTree tree = new ResourceTree(getLocalManager(), workManager.getLock(), status, updateFlags); + IMoveDeleteHook hook = workspace.getMoveDeleteHook(); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_MOVE, this, destination, updateFlags)); + int depth = 0; + try { + depth = workManager.beginUnprotected(); + if (!hook.moveProject(tree, this, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2))) + tree.standardMoveProject(this, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2)); + } finally { + workManager.endUnprotected(depth); + } + // Invalidate the tree for further use by clients. + tree.makeInvalid(); + if (!tree.getStatus().isOK()) + throw new ResourceException(tree.getStatus()); + // make sure the move operation is remembered + workspace.getSaveManager().requestSnapshot(); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void open(int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String msg = NLS.bind(Messages.resources_opening_1, getName()); + monitor.beginTask(msg, Policy.totalWork); + monitor.subTask(msg); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, monitor); + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + int flags = getFlags(info); + checkExists(flags, true); + if (isOpen(flags)) + return; + + workspace.beginOperation(true); + // flush the build order early in case there is a problem + workspace.flushBuildOrder(); + info = (ProjectInfo) getResourceInfo(false, true); + info.set(M_OPEN); + //clear the unknown children immediately to avoid background refresh + boolean unknownChildren = info.isSet(M_CHILDREN_UNKNOWN); + if (unknownChildren) + info.clear(M_CHILDREN_UNKNOWN); + // the M_USED flag is used to indicate the difference between opening a project + // for the first time and opening it from a previous close (restoring it from disk) + boolean used = info.isSet(M_USED); + boolean snapshotLoaded = false; + if (!used && !workspace.getMetaArea().getRefreshLocationFor(this).toFile().exists()) { + //auto-load a refresh snapshot if it is set + final boolean hasSavedDescription = getLocalManager().hasSavedDescription(this); + if (hasSavedDescription) { + ProjectDescription updatedDesc = info.getDescription(); + if (updatedDesc != null) { + URI autoloadURI = updatedDesc.getSnapshotLocationURI(); + if (autoloadURI != null) { + try { + autoloadURI = getPathVariableManager().resolveURI(autoloadURI); + internalLoadSnapshot(SNAPSHOT_TREE, autoloadURI, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + snapshotLoaded = true; + } catch (CoreException ce) { + //Log non-existing autoload snapshot as warning only + String msgerr = NLS.bind(Messages.projRead_cannotReadSnapshot, getName(), ce.getLocalizedMessage()); + Policy.log(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, msgerr)); + } + } + } + } + } + boolean minorIssuesDuringRestore = false; + if (used) { + minorIssuesDuringRestore = workspace.getSaveManager().restore(this, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + } else { + info.set(M_USED); + //reconcile any links and groups in the project description + IStatus result = reconcileLinksAndGroups(info.getDescription()); + if (!result.isOK()) + throw new CoreException(result); + workspace.updateModificationStamp(info); + monitor.worked(Policy.opWork * (snapshotLoaded ? 15 : 20) / 100); + } + startup(); + //request a refresh if the project is new and has unknown members on disk + // or restore of the project is not fully successful + if ((!used && unknownChildren) || !minorIssuesDuringRestore) { + boolean refreshed = false; + if (!used) { + refreshed = workspace.getSaveManager().restoreFromRefreshSnapshot(this, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + if (refreshed) { // account for the refresh work + monitor.worked(Policy.opWork * 60 / 100); + } + } + //refresh either in background or foreground + if (!refreshed) { + if ((updateFlags & IResource.BACKGROUND_REFRESH) != 0) { + workspace.refreshManager.refresh(this); + monitor.worked(Policy.opWork * 60 / 100); + } else { + refreshLocal(IResource.DEPTH_INFINITE, Policy.subMonitorFor(monitor, Policy.opWork * 60 / 100)); + } + } + } + //creation of this project may affect overlapping resources + workspace.getAliasManager().updateAliases(this, getStore(), IResource.DEPTH_INFINITE, 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 open(IProgressMonitor monitor) throws CoreException { + open(IResource.NONE, monitor); + } + + /** + * The project description file has changed on disk, resulting in a changed + * set of linked resources. Perform the necessary creations and deletions of + * links to bring the links in sync with those described in the project description. + * @param newDescription the new project description that may have + * changed link descriptions. + * @return status ok if everything went well, otherwise an ERROR multi-status + * describing the problems encountered. + */ + public IStatus reconcileLinksAndGroups(ProjectDescription newDescription) { + String msg = Messages.links_errorLinkReconcile; + HashMap newLinks = newDescription.getLinks(); + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.OPERATION_FAILED, msg, null); + //walk over old linked resources and remove those that are no longer defined + ProjectDescription oldDescription = internalGetDescription(); + if (oldDescription != null) { + HashMap oldLinks = oldDescription.getLinks(); + if (oldLinks != null) { + for (Iterator it = oldLinks.values().iterator(); it.hasNext();) { + LinkDescription oldLink = it.next(); + Resource oldLinkResource = (Resource) findMember(oldLink.getProjectRelativePath()); + if (oldLinkResource == null || !oldLinkResource.isLinked()) + continue; + LinkDescription newLink = null; + if (newLinks != null) + newLink = newLinks.get(oldLink.getProjectRelativePath()); + //if the new link is missing, or has different (raw) location or gender, then remove old link + if (newLink == null || !newLink.getLocationURI().equals(oldLinkResource.getRawLocationURI()) || newLink.getType() != oldLinkResource.getType()) { + try { + oldLinkResource.delete(IResource.NONE, null); + //refresh the resource, because removing a link can reveal a previously hidden resource in parent + oldLinkResource.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } + } + } + } + // walk over new links and groups and create if necessary + // Recreate them in order of the higher up in the tree hierarchy first, + // so we don't have to create intermediate directories that would turn + // out + // to be groups or link folders. + if (newLinks == null) + return status; + //sort links to avoid creating nested links before their parents + TreeSet newLinksAndGroups = new TreeSet<>(new Comparator() { + @Override + public int compare(LinkDescription arg0, LinkDescription arg1) { + int numberOfSegments0 = arg0.getProjectRelativePath().segmentCount(); + int numberOfSegments1 = arg1.getProjectRelativePath().segmentCount(); + if (numberOfSegments0 != numberOfSegments1) + return numberOfSegments0 - numberOfSegments1; + else if (arg0.equals(arg1)) + return 0; + + return -1; + } + + }); + if (newLinks != null) + newLinksAndGroups.addAll(newLinks.values()); + + for (Iterator it = newLinksAndGroups.iterator(); it.hasNext();) { + Object description = it.next(); + LinkDescription newLink = (LinkDescription) description; + try { + Resource toLink = workspace.newResource(getFullPath().append(newLink.getProjectRelativePath()), newLink.getType()); + IContainer parent = toLink.getParent(); + if (parent != null && !parent.exists() && parent.getType() == FOLDER) + ((Folder) parent).ensureExists(Policy.monitorFor(null)); + if (!toLink.exists() || !toLink.isLinked()) { + if (newLink.isGroup()) + ((Folder) toLink).create(IResource.REPLACE | IResource.VIRTUAL, true, null); + else + toLink.createLink(newLink.getLocationURI(), IResource.REPLACE | IResource.ALLOW_MISSING_LOCAL, null); + } + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } + return status; + } + + @Override + public void saveSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + //Project must be open such that variables can be resolved + checkAccessible(getFlags(getResourceInfo(false, false))); + //URI must not be null and must not refer to undefined path variables + URI resolvedSnapshotLocation = getPathVariableManager().resolveURI(snapshotLocation); + if (resolvedSnapshotLocation == null || !resolvedSnapshotLocation.isAbsolute()) { + String message = NLS.bind(Messages.projRead_badSnapshotLocation, resolvedSnapshotLocation); + throw new CoreException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, message, null)); + } + if ((options & SNAPSHOT_TREE) != 0) { + // write a snapshot of refresh information + try { + IProgressMonitor sub = Policy.subMonitorFor(monitor, Policy.opWork / 2); + workspace.getSaveManager().saveRefreshSnapshot(this, resolvedSnapshotLocation, sub); + } catch (OperationCanceledException e) { + //workspace.getWorkManager().operationCanceled(); + throw e; + } + } + if ((options & SNAPSHOT_SET_AUTOLOAD) != 0) { + IProgressMonitor sub = Policy.subMonitorFor(monitor, Policy.opWork / 2); + //Make absolute URI inside the project relative + if (snapshotLocation != null && snapshotLocation.isAbsolute()) { + snapshotLocation = getPathVariableManager().convertToRelative(snapshotLocation, false, "PROJECT_LOC"); //$NON-NLS-1$ + } + IProjectDescription desc = getDescription(); + ((ProjectDescription) desc).setSnapshotLocationURI(snapshotLocation); + setDescription(desc, IResource.KEEP_HISTORY | IResource.AVOID_NATURE_CONFIG, sub); + } + } finally { + monitor.done(); + } + } + + @Override + public void setDescription(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + // FIXME - update flags should be honored: + // KEEP_HISTORY means capture .project file in local history + // FORCE means overwrite any existing .project file + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask(Messages.resources_setDesc, Policy.totalWork); + ISchedulingRule rule = null; + if ((updateFlags & IResource.AVOID_NATURE_CONFIG) != 0) + rule = workspace.getRuleFactory().modifyRule(this); + else + rule = workspace.getRoot(); + try { + //need to use root rule because nature configuration calls third party code + workspace.prepareOperation(rule, monitor); + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + //if nothing has changed, we don't need to do anything + ProjectDescription oldDescription = internalGetDescription(); + ProjectDescription newDescription = (ProjectDescription) description; + boolean hasPublicChanges = oldDescription.hasPublicChanges(newDescription); + boolean hasPrivateChanges = oldDescription.hasPrivateChanges(newDescription); + if (!hasPublicChanges && !hasPrivateChanges) + return; + checkDescription(this, newDescription, false); + //If we're out of sync and !FORCE, then fail. + //If the file is missing, we want to write the new description then throw an exception. + boolean hadSavedDescription = true; + if (((updateFlags & IResource.FORCE) == 0)) { + hadSavedDescription = getLocalManager().hasSavedDescription(this); + if (hadSavedDescription && !getLocalManager().isDescriptionSynchronized(this)) { + String message = NLS.bind(Messages.resources_projectDescSync, getName()); + throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, getFullPath(), message, null); + } + } + //see if we have an old .prj file + if (!hadSavedDescription) + hadSavedDescription = workspace.getMetaArea().hasSavedProject(this); + workspace.beginOperation(true); + MultiStatus status = basicSetDescription(newDescription, updateFlags); + if (hadSavedDescription && !status.isOK()) + throw new CoreException(status); + //write the new description to the .project file + writeDescription(oldDescription, updateFlags, hasPublicChanges, hasPrivateChanges); + //increment the content id even for private changes + info = getResourceInfo(false, true); + info.incrementContentId(); + workspace.updateModificationStamp(info); + if (!hadSavedDescription) { + String msg = NLS.bind(Messages.resources_missingProjectMetaRepaired, getName()); + status.merge(new ResourceStatus(IResourceStatus.MISSING_DESCRIPTION_REPAIRED, getFullPath(), msg)); + } + if (!status.isOK()) + throw new CoreException(status); + } finally { + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void setDescription(IProjectDescription description, IProgressMonitor monitor) throws CoreException { + // funnel all operations to central method + setDescription(description, IResource.KEEP_HISTORY, monitor); + } + + /** + * Restore the non-persisted state for the project. For example, read and set + * the description from the local meta area. Also, open the property store etc. + * This method is used when an open project is restored and so emulates + * the behaviour of open(). + */ + protected void startup() throws CoreException { + if (!isOpen()) + return; + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_OPEN, this)); + } + + @Override + public void touch(IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_touch, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, monitor); + workspace.beginOperation(true); + super.touch(Policy.subMonitorFor(monitor, Policy.opWork)); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + /** + * The project description file on disk is better than the description in memory. + * Make sure the project description in memory is synchronized with the + * description file contents. + */ + protected void updateDescription() throws CoreException { + if (ProjectDescription.isWriting) + return; + ProjectDescription.isReading = true; + try { + ProjectDescription description = getLocalManager().read(this, false); + //links can only be created if the project is open + IStatus result = null; + if (isOpen()) + result = reconcileLinksAndGroups(description); + internalSetDescription(description, true); + if (result != null && !result.isOK()) + throw new CoreException(result); + } finally { + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); + ProjectDescription.isReading = false; + } + } + + /** + * Writes the project's current description file to disk. + */ + public void writeDescription(int updateFlags) throws CoreException { + writeDescription(internalGetDescription(), updateFlags, true, true); + } + + /** + * Writes the project description file to disk. This is the only method + * that should ever be writing the description, because it ensures that + * the description isn't then immediately discovered as an incoming + * change and read back from disk. + * @param description The description to write + * @param updateFlags The write operation update flags + * @param hasPublicChanges Whether the public sections of the description have changed + * @param hasPrivateChanges Whether the private sections of the description have changed + * @exception CoreException On failure to write the description + */ + public void writeDescription(IProjectDescription description, int updateFlags, boolean hasPublicChanges, boolean hasPrivateChanges) throws CoreException { + if (ProjectDescription.isReading) + return; + ProjectDescription.isWriting = true; + try { + getLocalManager().internalWrite(this, description, updateFlags, hasPublicChanges, hasPrivateChanges); + } finally { + ProjectDescription.isWriting = false; + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java new file mode 100644 index 0000000000..8965515cb4 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java @@ -0,0 +1,245 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.internal.utils.Cache; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ProjectScope; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy; +import org.eclipse.core.runtime.content.IContentTypeMatcher; +import org.eclipse.core.runtime.preferences.*; +import org.osgi.service.prefs.BackingStoreException; +import org.osgi.service.prefs.Preferences; + +/** + * Manages project-specific content type behavior. + * + * @see ContentDescriptionManager + * @see org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy + * @since 3.1 + */ +public class ProjectContentTypes { + + /** + * A project-aware content type selection policy. + * This class is also a dynamic scope context that will delegate to either + * project or instance scope depending on whether project specific settings were enabled + * for the project in question. + */ + private class ProjectContentTypeSelectionPolicy implements ISelectionPolicy, IScopeContext { + // corresponding project + private Project project; + // cached project scope + private IScopeContext projectScope; + + public ProjectContentTypeSelectionPolicy(Project project) { + this.project = project; + this.projectScope = new ProjectScope(project); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof IScopeContext)) + return false; + IScopeContext other = (IScopeContext) obj; + if (!getName().equals(other.getName())) + return false; + IPath location = getLocation(); + return location == null ? other.getLocation() == null : location.equals(other.getLocation()); + } + + private IScopeContext getDelegate() { + if (!usesContentTypePreferences(project.getName())) + return InstanceScope.INSTANCE; + return projectScope; + } + + @Override + public IPath getLocation() { + return getDelegate().getLocation(); + } + + @Override + public String getName() { + return getDelegate().getName(); + } + + @Override + public IEclipsePreferences getNode(String qualifier) { + return getDelegate().getNode(qualifier); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public IContentType[] select(IContentType[] candidates, boolean fileName, boolean content) { + return ProjectContentTypes.this.select(project, candidates, fileName, content); + } + } + + private static final String CONTENT_TYPE_PREF_NODE = "content-types"; //$NON-NLS-1$ + + private static final String PREF_LOCAL_CONTENT_TYPE_SETTINGS = "enabled"; //$NON-NLS-1$ + private static final Preferences PROJECT_SCOPE = Platform.getPreferencesService().getRootNode().node(ProjectScope.SCOPE); + private Cache contentTypesPerProject; + private Workspace workspace; + + static boolean usesContentTypePreferences(String projectName) { + try { + // be careful looking up for our node so not to create any nodes as side effect + Preferences node = PROJECT_SCOPE; + //TODO once bug 90500 is fixed, should be simpler + // for now, take the long way + if (!node.nodeExists(projectName)) + return false; + node = node.node(projectName); + if (!node.nodeExists(Platform.PI_RUNTIME)) + return false; + node = node.node(Platform.PI_RUNTIME); + if (!node.nodeExists(CONTENT_TYPE_PREF_NODE)) + return false; + node = node.node(CONTENT_TYPE_PREF_NODE); + return node.getBoolean(PREF_LOCAL_CONTENT_TYPE_SETTINGS, false); + } catch (BackingStoreException e) { + // exception treated when retrieving the project preferences + } + return false; + } + + public ProjectContentTypes(Workspace workspace) { + this.workspace = workspace; + // keep cache small + this.contentTypesPerProject = new Cache(5, 30, 0.4); + } + + /** + * Collect content types associated to the natures configured for the given project. + */ + private Set collectAssociatedContentTypes(Project project) { + String[] enabledNatures = workspace.getNatureManager().getEnabledNatures(project); + if (enabledNatures.length == 0) + return Collections.EMPTY_SET; + Set related = new HashSet<>(enabledNatures.length); + for (int i = 0; i < enabledNatures.length; i++) { + ProjectNatureDescriptor descriptor = (ProjectNatureDescriptor) workspace.getNatureDescriptor(enabledNatures[i]); + if (descriptor == null) + // no descriptor found for the nature, skip it + continue; + String[] natureContentTypes = descriptor.getContentTypeIds(); + for (int j = 0; j < natureContentTypes.length; j++) + // collect associate content types + related.add(natureContentTypes[j]); + } + return related; + } + + public void contentTypePreferencesChanged(IProject project) { + final ProjectInfo info = (ProjectInfo) ((Project) project).getResourceInfo(false, false); + if (info != null) + info.setMatcher(null); + } + + /** + * Creates a content type matcher for the given project. Takes natures and user settings into account. + */ + private IContentTypeMatcher createMatcher(Project project) { + ProjectContentTypeSelectionPolicy projectContentTypeSelectionPolicy = new ProjectContentTypeSelectionPolicy(project); + return Platform.getContentTypeManager().getMatcher(projectContentTypeSelectionPolicy, projectContentTypeSelectionPolicy); + } + + @SuppressWarnings({"unchecked"}) + private Set getAssociatedContentTypes(Project project) { + final ResourceInfo info = project.getResourceInfo(false, false); + if (info == null) + // the project has been deleted + return null; + final String projectName = project.getName(); + synchronized (contentTypesPerProject) { + Cache.Entry entry = contentTypesPerProject.getEntry(projectName); + if (entry != null) + // we have an entry... + if (entry.getTimestamp() == info.getContentId()) + // ...and it is not stale, so just return it + return (Set) entry.getCached(); + // no cached information found, have to collect associated content types + Set result = collectAssociatedContentTypes(project); + if (entry == null) + // there was no entry before - create one + entry = contentTypesPerProject.addEntry(projectName, result, info.getContentId()); + else { + // just update the existing entry + entry.setTimestamp(info.getContentId()); + entry.setCached(result); + } + return result; + } + } + + public IContentTypeMatcher getMatcherFor(Project project) throws CoreException { + ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, false); + //fail if project has been deleted concurrently + if (info == null) + project.checkAccessible(project.getFlags(info)); + IContentTypeMatcher matcher = info.getMatcher(); + if (matcher != null) + return matcher; + matcher = createMatcher(project); + info.setMatcher(matcher); + return matcher; + } + + /** + * Implements project specific, nature-based selection policy. No content types are vetoed. + * + * The criteria for this policy is as follows: + *
            + *
          1. associated content types should appear before non-associated content types
          2. + *
          3. otherwise, relative ordering should be preserved.
          4. + *
          + * + * @see org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy + */ + final IContentType[] select(Project project, IContentType[] candidates, boolean fileName, boolean content) { + // since no vetoing is done here, don't go further if there is nothing to sort + if (candidates.length < 2) + return candidates; + final Set associated = getAssociatedContentTypes(project); + if (associated == null || associated.isEmpty()) + // project has no content types associated + return candidates; + int associatedCount = 0; + for (int i = 0; i < candidates.length; i++) + // is it an associated content type? + if (associated.contains(candidates[i].getId())) { + // need to move it to the right spot (unless all types visited so far are associated as well) + if (associatedCount < i) { + final IContentType promoted = candidates[i]; + // move all non-associated content types before it one one position up... + for (int j = i; j > associatedCount; j--) + candidates[j] = candidates[j - 1]; + // ...so there is an empty spot for the content type we are promoting + candidates[associatedCount] = promoted; + } + associatedCount++; + } + return candidates; + } +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java new file mode 100644 index 0000000000..7bbc9dff6f --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java @@ -0,0 +1,877 @@ +/******************************************************************************* + * 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) - [245937] setLinkLocation() detects non-change + * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * Broadcom Corporation - build configurations and references + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.util.*; +import java.util.Map.Entry; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +public class ProjectDescription extends ModelObject implements IProjectDescription { + // constants + private static final IBuildConfiguration[] EMPTY_BUILD_CONFIG_REFERENCE_ARRAY = new IBuildConfiguration[0]; + private static final ICommand[] EMPTY_COMMAND_ARRAY = new ICommand[0]; + private static final IProject[] EMPTY_PROJECT_ARRAY = new IProject[0]; + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final String EMPTY_STR = ""; //$NON-NLS-1$ + + protected static boolean isReading = false; + + //flags to indicate when we are in the middle of reading or writing a + // workspace description + //these can be static because only one description can be read at once. + protected static boolean isWriting = false; + protected ICommand[] buildSpec = EMPTY_COMMAND_ARRAY; + protected String comment = EMPTY_STR; + + // Build configuration + References state + /** Id of the currently active build configuration */ + protected String activeConfiguration = IBuildConfiguration.DEFAULT_CONFIG_NAME; + /** + * The 'real' build configuration names set on this project. + * This doesn't contain the generated 'default' build configuration with name + * {@link IBuildConfiguration#DEFAULT_CONFIG_NAME} + * when no build configurations have been defined. + */ + protected String[] configNames = EMPTY_STRING_ARRAY; + // Static + Dynamic project level references + protected IProject[] staticRefs = EMPTY_PROJECT_ARRAY; + protected IProject[] dynamicRefs = EMPTY_PROJECT_ARRAY; + /** Map from config name in this project -> build configurations in other projects */ + protected HashMap dynamicConfigRefs = new HashMap<>(1); + + // Cache of the build configurations + protected volatile IBuildConfiguration[] cachedBuildConfigs; + // Cached build configuration references. Not persisted. + protected Map cachedConfigRefs = Collections.synchronizedMap(new HashMap(1)); + // Cached project level references. + protected volatile IProject[] cachedRefs = null; + + /** + * Map of (IPath -> LinkDescription) pairs for each linked resource + * in this project, where IPath is the project relative path of the resource. + */ + protected HashMap linkDescriptions = null; + + /** + * Map of (IPath -> LinkedList) pairs for each filtered resource + * in this project, where IPath is the project relative path of the resource. + */ + protected HashMap> filterDescriptions = null; + + /** + * Map of (String -> VariableDescription) pairs for each variable in this + * project, where String is the name of the variable. + */ + protected HashMap variableDescriptions = null; + + // fields + protected URI location = null; + protected String[] natures = EMPTY_STRING_ARRAY; + protected URI snapshotLocation = null; + + public ProjectDescription() { + super(); + } + + @Override + @SuppressWarnings({"unchecked"}) + public Object clone() { + ProjectDescription clone = (ProjectDescription) super.clone(); + //don't want the clone to have access to our internal link locations table or builders + clone.linkDescriptions = null; + clone.filterDescriptions = null; + if (variableDescriptions != null) + clone.variableDescriptions = (HashMap) variableDescriptions.clone(); + clone.buildSpec = getBuildSpec(true); + clone.dynamicConfigRefs = (HashMap) dynamicConfigRefs.clone(); + clone.cachedConfigRefs = Collections.synchronizedMap(new HashMap(1)); + clone.clearCachedReferences(null); + return clone; + } + + /** + * Clear cached references for the specified build config name + * or all if configName is null. + */ + private void clearCachedReferences(String configName) { + if (configName == null) + cachedConfigRefs.clear(); + else + cachedConfigRefs.remove(configName); + cachedRefs = null; + } + + /** + * Returns a copy of the given array of build configs with all duplicates removed + */ + private IBuildConfiguration[] copyAndRemoveDuplicates(IBuildConfiguration[] values) { + Set set = new LinkedHashSet<>(Arrays.asList(values)); + return set.toArray(new IBuildConfiguration[set.size()]); + } + + /** + * Returns a copy of the given array with all duplicates removed + */ + private IProject[] copyAndRemoveDuplicates(IProject[] projects) { + IProject[] result = new IProject[projects.length]; + int count = 0; + next: for (int i = 0; i < projects.length; i++) { + IProject project = projects[i]; + // scan to see if there are any other projects by the same name + for (int j = 0; j < count; j++) + if (project.equals(result[j])) + continue next; + // not found + result[count++] = project; + } + if (count < projects.length) { + //shrink array + IProject[] reduced = new IProject[count]; + System.arraycopy(result, 0, reduced, 0, count); + return reduced; + } + return result; + } + + /** + * Helper to turn an array of projects into an array of {@link IBuildConfiguration} to the + * projects' active configuration + * Order is preserved - the buildConfigs appear for each project in the order + * that the projects were specified. + * @param projects projects to get the active configuration from + * @return collection of build config references + */ + private Collection getBuildConfigReferencesFromProjects(IProject[] projects) { + List refs = new ArrayList<>(projects.length); + for (int i = 0; i < projects.length; i++) + refs.add(new BuildConfiguration(projects[i], null)); + return refs; + } + + /** + * Helper to fetch projects from an array of build configuration references + * @param refs + * @return List + */ + private Collection getProjectsFromBuildConfigRefs(IBuildConfiguration[] refs) { + LinkedHashSet projects = new LinkedHashSet<>(refs.length); + for (int i = 0; i < refs.length; i++) + projects.add(refs[i].getProject()); + return projects; + } + + public String getActiveBuildConfig() { + return activeConfiguration; + } + + /** + * Returns the union of the description's static and dynamic project references, + * with duplicates omitted. The calculation is optimized by caching the result + * Call the configuration based implementation. + * @see #getAllBuildConfigReferences(String, boolean) + */ + public IProject[] getAllReferences(boolean makeCopy) { + IProject[] projRefs = cachedRefs; + if (projRefs == null) { + IBuildConfiguration[] refs; + if (hasBuildConfig(activeConfiguration)) + refs = getAllBuildConfigReferences(activeConfiguration, false); + else if (configNames.length > 0) + refs = getAllBuildConfigReferences(configNames[0], false); + else + // No build configuration => fall-back to default + refs = getAllBuildConfigReferences(IBuildConfiguration.DEFAULT_CONFIG_NAME, false); + Collection l = getProjectsFromBuildConfigRefs(refs); + projRefs = cachedRefs = l.toArray(new IProject[l.size()]); + } + //still need to copy the result to prevent tampering with the cache + return makeCopy ? (IProject[]) projRefs.clone() : projRefs; + } + + /** + * The main entrance point to fetch the full set of Project references. + * + * Returns the union of all the description's references. Includes static and dynamic + * project level references as well as build configuration references for the configuration + * with the given id. + * Duplicates are omitted. The calculation is optimized by caching the result. + * Note that these BuildConfiguration references may have null name. They must + * be resolved using {@link BuildConfiguration#getBuildConfig()} before use. + * Returns an empty array if the given configName does not exist in the description. + */ + public IBuildConfiguration[] getAllBuildConfigReferences(String configName, boolean makeCopy) { + if (!hasBuildConfig(configName)) + return EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; + IBuildConfiguration[] refs = cachedConfigRefs.get(configName); + if (refs == null) { + Set references = new LinkedHashSet<>(); + IBuildConfiguration[] dynamicBuildConfigs = dynamicConfigRefs.containsKey(configName) ? dynamicConfigRefs.get(configName) : EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; + Collection dynamic = getBuildConfigReferencesFromProjects(dynamicRefs); + Collection statik = getBuildConfigReferencesFromProjects(staticRefs); + + // Combine all references: + // New build config references (which only come in dynamic form) trump all others. + references.addAll(Arrays.asList(dynamicBuildConfigs)); + // We preserve the previous order of static project references before dynamic project references + references.addAll(statik); + references.addAll(dynamic); + refs = references.toArray(new IBuildConfiguration[references.size()]); + cachedConfigRefs.put(configName, refs); + } + return makeCopy ? (IBuildConfiguration[]) refs.clone() : refs; + } + + /** + * Used by Project to get the buildConfigs on the description. + * @return the project configurations + */ + public IBuildConfiguration[] getBuildConfigs(IProject project, boolean makeCopy) { + IBuildConfiguration[] configs = cachedBuildConfigs; + // Ensure project is up to date in the cache + if (configs != null && !project.equals(configs[0].getProject())) + configs = null; + if (configs == null) { + if (configNames.length == 0) + configs = new IBuildConfiguration[] {new BuildConfiguration(project)}; + else { + configs = new IBuildConfiguration[configNames.length]; + for (int i = 0; i < configs.length; i++) + configs[i] = new BuildConfiguration(project, configNames[i]); + } + cachedBuildConfigs = configs; + } + return makeCopy ? (IBuildConfiguration[]) configs.clone() : configs; + } + + @Override + public IBuildConfiguration[] getBuildConfigReferences(String configName) { + return getBuildConfigRefs(configName, true); + } + + public IBuildConfiguration[] getBuildConfigRefs(String configName, boolean makeCopy) { + if (!hasBuildConfig(configName) || !dynamicConfigRefs.containsKey(configName)) + return EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; + + return makeCopy ? (IBuildConfiguration[]) dynamicConfigRefs.get(configName).clone() : dynamicConfigRefs.get(configName); + } + + /** + * Returns the build configuration references map + * @param makeCopy + */ + @SuppressWarnings({"unchecked"}) + public Map getBuildConfigReferences(boolean makeCopy) { + return makeCopy ? (Map) dynamicConfigRefs.clone() : dynamicConfigRefs; + } + + @Override + public ICommand[] getBuildSpec() { + return getBuildSpec(true); + } + + public ICommand[] getBuildSpec(boolean makeCopy) { + //thread safety: copy reference in case of concurrent write + ICommand[] oldCommands = this.buildSpec; + if (oldCommands == null) + return EMPTY_COMMAND_ARRAY; + if (!makeCopy) + return oldCommands; + ICommand[] result = new ICommand[oldCommands.length]; + for (int i = 0; i < result.length; i++) + result[i] = (ICommand) ((BuildCommand) oldCommands[i]).clone(); + return result; + } + + @Override + public String getComment() { + return comment; + } + + @Override + public IProject[] getDynamicReferences() { + return getDynamicReferences(true); + } + + public IProject[] getDynamicReferences(boolean makeCopy) { + return makeCopy ? (IProject[]) dynamicRefs.clone() : dynamicRefs; + } + + /** + * Returns the link location for the given resource name. Returns null if + * no such link exists. + */ + public URI getLinkLocationURI(IPath aPath) { + if (linkDescriptions == null) + return null; + LinkDescription desc = linkDescriptions.get(aPath); + return desc == null ? null : desc.getLocationURI(); + } + + /** + * Returns the filter for the given resource name. Returns null if + * no such filter exists. + */ + synchronized public LinkedList getFilter(IPath aPath) { + if (filterDescriptions == null) + return null; + return filterDescriptions.get(aPath); + } + + /** + * Returns the map of link descriptions (IPath (project relative path) -> LinkDescription). + * Since this method is only used internally, it never creates a copy. + * Returns null if the project does not have any linked resources. + */ + public HashMap getLinks() { + return linkDescriptions; + } + + /** + * Returns the map of filter descriptions (IPath (project relative path) -> LinkedList). + * Since this method is only used internally, it never creates a copy. + * Returns null if the project does not have any filtered resources. + */ + public HashMap> getFilters() { + return filterDescriptions; + } + + /** + * Returns the map of variable descriptions (String (variable name) -> + * VariableDescription). Since this method is only used internally, it never + * creates a copy. Returns null if the project does not have any variables. + */ + public HashMap getVariables() { + return variableDescriptions; + } + + /** + * @see IProjectDescription#getLocation() + * @deprecated + */ + @Override + @Deprecated + public IPath getLocation() { + if (location == null) + return null; + return FileUtil.toPath(location); + } + + @Override + public URI getLocationURI() { + return location; + } + + @Override + public String[] getNatureIds() { + return getNatureIds(true); + } + + public String[] getNatureIds(boolean makeCopy) { + if (natures == null) + return EMPTY_STRING_ARRAY; + return makeCopy ? (String[]) natures.clone() : natures; + } + + @Override + public IProject[] getReferencedProjects() { + return getReferencedProjects(true); + } + + public IProject[] getReferencedProjects(boolean makeCopy) { + if (staticRefs == null) + return EMPTY_PROJECT_ARRAY; + return makeCopy ? (IProject[]) staticRefs.clone() : staticRefs; + } + + /** + * Returns the URI to load a resource snapshot from. + * May return null if no snapshot is set. + *

          + * EXPERIMENTAL. This constant has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. Please do not use this API without + * consulting with the Platform Core team. + *

          + * @return the snapshot location URI, + * or null. + * @see IProject#loadSnapshot(int, URI, IProgressMonitor) + * @see #setSnapshotLocationURI(URI) + * @since 3.6 + */ + public URI getSnapshotLocationURI() { + return snapshotLocation; + } + + @Override + public boolean hasNature(String natureID) { + String[] natureIDs = getNatureIds(false); + for (int i = 0; i < natureIDs.length; ++i) + if (natureIDs[i].equals(natureID)) + return true; + return false; + } + + /** + * Helper method to compare two maps of Configuration Name -> IBuildConfigurationReference[] + * @return boolean indicating if there are differences between the two maps + */ + private static boolean configRefsHaveChanges(Map m1, Map m2) { + if (m1.size() != m2.size()) + return true; + for (Iterator> it = m1.entrySet().iterator(); it.hasNext();) { + Entry e = it.next(); + if (!m2.containsKey(e.getKey())) + return true; + if (!Arrays.equals(e.getValue(), m2.get(e.getKey()))) + return true; + } + return false; + } + + /** + * Internal method to check if the description has a given build configuration. + */ + boolean hasBuildConfig(String buildConfigName) { + Assert.isNotNull(buildConfigName); + if (configNames.length == 0) + return IBuildConfiguration.DEFAULT_CONFIG_NAME.equals(buildConfigName); + for (int i = 0; i < configNames.length; i++) + if (configNames[i].equals(buildConfigName)) + return true; + return false; + } + + /** + * Returns true if any private attributes of the description have changed. + * Private attributes are those that are not stored in the project description + * file (.project). + */ + public boolean hasPrivateChanges(ProjectDescription description) { + if (location == null) { + if (description.location != null) + return true; + } else if (!location.equals(description.location)) + return true; + + if (!Arrays.equals(dynamicRefs, description.dynamicRefs)) + return true; + + // Build Configuration state + if (!activeConfiguration.equals(description.activeConfiguration)) + return true; + if (!Arrays.equals(configNames, description.configNames)) + return true; + // Configuration level references + if (configRefsHaveChanges(dynamicConfigRefs, description.dynamicConfigRefs)) + return true; + + return false; + } + + /** + * Returns true if any public attributes of the description have changed. + * Public attributes are those that are stored in the project description + * file (.project). + */ + public boolean hasPublicChanges(ProjectDescription description) { + if (!getName().equals(description.getName())) + return true; + if (!comment.equals(description.getComment())) + return true; + //don't bother optimizing if the order has changed + if (!Arrays.equals(buildSpec, description.getBuildSpec(false))) + return true; + if (!Arrays.equals(staticRefs, description.getReferencedProjects(false))) + return true; + if (!Arrays.equals(natures, description.getNatureIds(false))) + return true; + + HashMap> otherFilters = description.getFilters(); + if ((filterDescriptions == null) && (otherFilters != null)) + return otherFilters != null; + if ((filterDescriptions != null) && !filterDescriptions.equals(otherFilters)) + return true; + + HashMap otherVariables = description.getVariables(); + if ((variableDescriptions == null) && (otherVariables != null)) + return true; + if ((variableDescriptions != null) && !variableDescriptions.equals(otherVariables)) + return true; + + final HashMap otherLinks = description.getLinks(); + if (linkDescriptions != otherLinks) { + if (linkDescriptions == null || !linkDescriptions.equals(otherLinks)) + return true; + } + + final URI otherSnapshotLoc = description.getSnapshotLocationURI(); + if (snapshotLocation != otherSnapshotLoc) { + if (snapshotLocation == null || !snapshotLocation.equals(otherSnapshotLoc)) + return true; + } + return false; + } + + @Override + public ICommand newCommand() { + return new BuildCommand(); + } + + @Override + public void setActiveBuildConfig(String configName) { + Assert.isNotNull(configName); + if (!configName.equals(activeConfiguration)) + clearCachedReferences(null); + activeConfiguration = configName; + } + + @Override + public void setBuildSpec(ICommand[] value) { + Assert.isLegal(value != null); + //perform a deep copy in case clients perform further changes to the command + ICommand[] result = new ICommand[value.length]; + for (int i = 0; i < result.length; i++) { + result[i] = (ICommand) ((BuildCommand) value[i]).clone(); + //copy the reference to any builder instance from the old build spec + //to preserve builder states if possible. + for (int j = 0; j < buildSpec.length; j++) { + if (result[i].equals(buildSpec[j])) { + ((BuildCommand) result[i]).setBuilders(((BuildCommand) buildSpec[j]).getBuilders()); + break; + } + } + } + buildSpec = result; + } + + @Override + public void setComment(String value) { + comment = value; + } + + @Override + public void setDynamicReferences(IProject[] value) { + Assert.isLegal(value != null); + dynamicRefs = copyAndRemoveDuplicates(value); + clearCachedReferences(null); + } + + public void setBuildConfigReferences(HashMap refs) { + dynamicConfigRefs = new HashMap<>(refs); + clearCachedReferences(null); + } + + @Override + public void setBuildConfigReferences(String configName, IBuildConfiguration[] references) { + Assert.isLegal(configName != null); + Assert.isLegal(references != null); + if (!hasBuildConfig(configName)) + return; + dynamicConfigRefs.put(configName, copyAndRemoveDuplicates(references)); + clearCachedReferences(configName); + } + + @Override + public void setBuildConfigs(String[] names) { + // Remove references for deleted buildConfigs + LinkedHashSet buildConfigNames = new LinkedHashSet<>(); + + if (names == null || names.length == 0) { + configNames = EMPTY_STRING_ARRAY; + buildConfigNames.add(IBuildConfiguration.DEFAULT_CONFIG_NAME); + } else { + // Filter out duplicates + for (String n : names) { + Assert.isLegal(n != null); + buildConfigNames.add(n); + } + + if (buildConfigNames.size() == 1 && ((buildConfigNames.iterator().next())).equals(IBuildConfiguration.DEFAULT_CONFIG_NAME)) + configNames = EMPTY_STRING_ARRAY; + else + configNames = buildConfigNames.toArray(new String[buildConfigNames.size()]); + } + + // Remove references for deleted buildConfigs + boolean modified = dynamicConfigRefs.keySet().retainAll(buildConfigNames); + if (modified) + clearCachedReferences(null); + // Clear the cached IBuildConfiguration[] + cachedBuildConfigs = null; + } + + /** + * Sets the map of link descriptions (String name -> LinkDescription). + * Since this method is only used internally, it never creates a copy. May + * pass null if this project does not have any linked resources + */ + public void setLinkDescriptions(HashMap linkDescriptions) { + this.linkDescriptions = linkDescriptions; + } + + /** + * Sets the map of filter descriptions (String name -> LinkedList). + * Since this method is only used internally, it never creates a copy. May + * pass null if this project does not have any filtered resources + */ + public void setFilterDescriptions(HashMap> filterDescriptions) { + this.filterDescriptions = filterDescriptions; + } + + /** + * Sets the map of variable descriptions (String name -> + * VariableDescription). Since this method is only used internally, it never + * creates a copy. May pass null if this project does not have any variables + */ + public void setVariableDescriptions(HashMap variableDescriptions) { + this.variableDescriptions = variableDescriptions; + } + + /** + * Sets the description of a link. Setting to a description of null will + * remove the link from the project description. + * @return true if the description was actually changed, + * false otherwise. + * @since 3.5 returns boolean (was void before) + */ + @SuppressWarnings({"unchecked"}) + public boolean setLinkLocation(IPath path, LinkDescription description) { + HashMap tempMap = linkDescriptions; + if (description != null) { + //addition or modification + if (tempMap == null) + tempMap = new HashMap<>(10); + else + //copy on write to protect against concurrent read + tempMap = (HashMap) tempMap.clone(); + Object oldValue = tempMap.put(path, description); + if (oldValue != null && description.equals(oldValue)) { + //not actually changed anything + return false; + } + linkDescriptions = tempMap; + } else { + //removal + if (tempMap == null) + return false; + //copy on write to protect against concurrent access + HashMap newMap = (HashMap) tempMap.clone(); + Object oldValue = newMap.remove(path); + if (oldValue == null) { + //not actually changed anything + return false; + } + linkDescriptions = newMap.size() == 0 ? null : newMap; + } + return true; + } + + /** + * Add the description of a filter. Setting to a description of null will + * remove the filter from the project description. + */ + synchronized public void addFilter(IPath path, FilterDescription description) { + Assert.isNotNull(description); + if (filterDescriptions == null) + filterDescriptions = new HashMap<>(10); + LinkedList descList = filterDescriptions.get(path); + if (descList == null) { + descList = new LinkedList<>(); + filterDescriptions.put(path, descList); + } + descList.add(description); + } + + /** + * Add the description of a filter. Setting to a description of null will + * remove the filter from the project description. + */ + synchronized public void removeFilter(IPath path, FilterDescription description) { + if (filterDescriptions != null) { + LinkedList descList = filterDescriptions.get(path); + if (descList != null) { + descList.remove(description); + if (descList.size() == 0) { + filterDescriptions.remove(path); + if (filterDescriptions.size() == 0) + filterDescriptions = null; + } + } + } + } + + /** + * Sets the description of a variable. Setting to a description of null will + * remove the variable from the project description. + * @return true if the description was actually changed, + * false otherwise. + * @since 3.5 + */ + @SuppressWarnings({"unchecked"}) + public boolean setVariableDescription(String name, VariableDescription description) { + HashMap tempMap = variableDescriptions; + if (description != null) { + // addition or modification + if (tempMap == null) + tempMap = new HashMap<>(10); + else + // copy on write to protect against concurrent read + tempMap = (HashMap) tempMap.clone(); + Object oldValue = tempMap.put(name, description); + if (oldValue != null && description.equals(oldValue)) { + //not actually changed anything + return false; + } + variableDescriptions = tempMap; + } else { + // removal + if (tempMap == null) + return false; + // copy on write to protect against concurrent access + HashMap newMap = (HashMap) tempMap.clone(); + Object oldValue = newMap.remove(name); + if (oldValue == null) { + //not actually changed anything + return false; + } + variableDescriptions = newMap.size() == 0 ? null : newMap; + } + return true; + } + + /** + * set the filters for a given resource. Setting to a description of null will + * remove the filter from the project description. + * @return true if the description was actually changed, + * false otherwise. + */ + synchronized public boolean setFilters(IPath path, LinkedList descriptions) { + if (descriptions != null) { + // addition + if (filterDescriptions == null) + filterDescriptions = new HashMap<>(10); + Object oldValue = filterDescriptions.put(path, descriptions); + if (oldValue != null && descriptions.equals(oldValue)) { + //not actually changed anything + return false; + } + } else { + // removal + if (filterDescriptions == null) + return false; + + Object oldValue = filterDescriptions.remove(path); + if (oldValue == null) { + //not actually changed anything + return false; + } + if (filterDescriptions.size() == 0) + filterDescriptions = null; + } + return true; + } + + @Override + public void setLocation(IPath path) { + this.location = path == null ? null : URIUtil.toURI(path); + } + + @Override + public void setLocationURI(URI location) { + this.location = location; + } + + @Override + public void setName(String value) { + super.setName(value); + } + + @Override + public void setNatureIds(String[] value) { + natures = value.clone(); + } + + @Override + public void setReferencedProjects(IProject[] value) { + Assert.isLegal(value != null); + staticRefs = copyAndRemoveDuplicates(value); + clearCachedReferences(null); + } + + /** + * Sets the location URI for a project snapshot that may be + * loaded automatically when the project is created in a workspace. + *

          + * EXPERIMENTAL. This method has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. Please do not use this API without + * consulting with the Platform Core team. + *

          + * @param snapshotLocation the location URI or + * null to clear the setting + * @see IProject#loadSnapshot(int, URI, IProgressMonitor) + * @see #getSnapshotLocationURI() + * @since 3.6 + */ + public void setSnapshotLocationURI(URI snapshotLocation) { + this.snapshotLocation = snapshotLocation; + } + + public URI getGroupLocationURI(IPath projectRelativePath) { + return LinkDescription.VIRTUAL_LOCATION; + } + + /** + * Updates the dynamic build configuration and reference state to that of the passed in + * description. + * Copies in: + *
            + *
          • Active configuration name
          • + *
          • Dynamic Project References
          • + *
          • Build configurations list
          • + *
          • Build Configuration References
          • + *
          + * @param description Project description to copy dynamic state from + * @return boolean indicating if anything changed requing re-calculation of WS build order + */ + public boolean updateDynamicState(ProjectDescription description) { + boolean changed = false; + if (!activeConfiguration.equals(description.activeConfiguration)) { + changed = true; + activeConfiguration = description.activeConfiguration; + } + if (!Arrays.equals(dynamicRefs, description.dynamicRefs)) { + changed = true; + setDynamicReferences(description.dynamicRefs); + } + if (!Arrays.equals(configNames, description.configNames)) { + changed = true; + setBuildConfigs(description.configNames); + } + if (configRefsHaveChanges(dynamicConfigRefs, description.dynamicConfigRefs)) { + changed = true; + dynamicConfigRefs = new HashMap<>(description.dynamicConfigRefs); + } + if (changed) + clearCachedReferences(null); + return changed; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java new file mode 100644 index 0000000000..c0d10d1d75 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java @@ -0,0 +1,1119 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import javax.xml.parsers.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.internal.localstore.SafeFileInputStream; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; +import org.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Reads serialized project descriptions. + * + * Note: Suppress warnings on whole class because of unusual use of objectStack. + */ +@SuppressWarnings({"unchecked"}) +public class ProjectDescriptionReader extends DefaultHandler implements IModelObjectConstants { + + //states + protected static final int S_BUILD_COMMAND = 0; + protected static final int S_BUILD_COMMAND_ARGUMENTS = 1; + protected static final int S_BUILD_COMMAND_NAME = 2; + protected static final int S_BUILD_COMMAND_TRIGGERS = 3; + protected static final int S_BUILD_SPEC = 4; + protected static final int S_DICTIONARY = 5; + protected static final int S_DICTIONARY_KEY = 6; + protected static final int S_DICTIONARY_VALUE = 7; + protected static final int S_INITIAL = 8; + protected static final int S_LINK = 9; + protected static final int S_LINK_LOCATION = 10; + protected static final int S_LINK_LOCATION_URI = 11; + protected static final int S_LINK_PATH = 12; + protected static final int S_LINK_TYPE = 13; + protected static final int S_LINKED_RESOURCES = 14; + protected static final int S_NATURE_NAME = 15; + protected static final int S_NATURES = 16; + protected static final int S_PROJECT_COMMENT = 17; + protected static final int S_PROJECT_DESC = 18; + protected static final int S_PROJECT_NAME = 19; + protected static final int S_PROJECTS = 20; + protected static final int S_REFERENCED_PROJECT_NAME = 21; + + protected static final int S_FILTERED_RESOURCES = 23; + protected static final int S_FILTER = 24; + protected static final int S_FILTER_ID = 25; + protected static final int S_FILTER_PATH = 26; + protected static final int S_FILTER_TYPE = 27; + + protected static final int S_MATCHER = 28; + protected static final int S_MATCHER_ID = 29; + protected static final int S_MATCHER_ARGUMENTS = 30; + + protected static final int S_VARIABLE_LIST = 31; + protected static final int S_VARIABLE = 32; + protected static final int S_VARIABLE_NAME = 33; + protected static final int S_VARIABLE_VALUE = 34; + + protected static final int S_SNAPSHOT_LOCATION = 35; + + /** + * Singleton sax parser factory + */ + private static SAXParserFactory singletonParserFactory; + + /** + * Singleton sax parser + */ + private static SAXParser singletonParser; + + protected final StringBuffer charBuffer = new StringBuffer(); + + protected Stack objectStack; + protected MultiStatus problems; + + /** + * The project we are reading the description for, or null if unknown. + */ + private final IProject project; + // The project description we are creating. + ProjectDescription projectDescription = null; + + protected int state = S_INITIAL; + + /** + * Returns the SAXParser to use when parsing project description files. + * @throws ParserConfigurationException + * @throws SAXException + */ + private static synchronized SAXParser createParser() throws ParserConfigurationException, SAXException { + //the parser can't be used concurrently, so only use singleton when workspace is locked + if (!isWorkspaceLocked()) + return createParserFactory().newSAXParser(); + if (singletonParser == null) { + singletonParser = createParserFactory().newSAXParser(); + } + return singletonParser; + } + + /** + * Returns the SAXParserFactory to use when parsing project description files. + * @throws ParserConfigurationException + */ + private static synchronized SAXParserFactory createParserFactory() throws ParserConfigurationException { + if (singletonParserFactory == null) { + singletonParserFactory = SAXParserFactory.newInstance(); + singletonParserFactory.setNamespaceAware(true); + try { + singletonParserFactory.setFeature("http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$ + } catch (SAXException e) { + // In case support for this feature is removed + } + } + return singletonParserFactory; + } + + private static boolean isWorkspaceLocked() { + try { + return ((Workspace) ResourcesPlugin.getWorkspace()).getWorkManager().isLockAlreadyAcquired(); + } catch (CoreException e) { + return false; + } + } + + public ProjectDescriptionReader() { + this.project = null; + } + + public ProjectDescriptionReader(IProject project) { + this.project = project; + } + + /** + * @see ContentHandler#characters(char[], int, int) + */ + @Override + public void characters(char[] chars, int offset, int length) { + //accumulate characters and process them when endElement is reached + charBuffer.append(chars, offset, length); + } + + /** + * End of an element that is part of a build command + */ + private void endBuildCommandElement(String elementName) { + if (elementName.equals(BUILD_COMMAND)) { + // Pop this BuildCommand off the stack. + BuildCommand command = (BuildCommand) objectStack.pop(); + // Add this BuildCommand to a array list of BuildCommands. + ArrayList commandList = (ArrayList) objectStack.peek(); + commandList.add(command); + state = S_BUILD_SPEC; + } + } + + /** + * End of an element that is part of a build spec + */ + private void endBuildSpecElement(String elementName) { + if (elementName.equals(BUILD_SPEC)) { + // Pop off the array list of BuildCommands and add them to the + // ProjectDescription which is the next thing on the stack. + ArrayList commands = (ArrayList) objectStack.pop(); + state = S_PROJECT_DESC; + if (commands.isEmpty()) + return; + ICommand[] commandArray = commands.toArray(new ICommand[commands.size()]); + projectDescription.setBuildSpec(commandArray); + } + } + + /** + * End a build triggers element and set the triggers for the current + * build command element. + */ + private void endBuildTriggersElement(String elementName) { + if (elementName.equals(BUILD_TRIGGERS)) { + state = S_BUILD_COMMAND; + BuildCommand command = (BuildCommand) objectStack.peek(); + //presence of this element indicates the builder is configurable + command.setConfigurable(true); + //clear all existing values + command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, false); + + //set new values according to value in the triggers element + StringTokenizer tokens = new StringTokenizer(charBuffer.toString(), ","); //$NON-NLS-1$ + while (tokens.hasMoreTokens()) { + String next = tokens.nextToken(); + if (next.toLowerCase().equals(TRIGGER_AUTO)) { + command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, true); + } else if (next.toLowerCase().equals(TRIGGER_CLEAN)) { + command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, true); + } else if (next.toLowerCase().equals(TRIGGER_FULL)) { + command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, true); + } else if (next.toLowerCase().equals(TRIGGER_INCREMENTAL)) { + command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, true); + } + } + } + } + + /** + * End of a dictionary element + */ + private void endDictionary(String elementName) { + if (elementName.equals(DICTIONARY)) { + // Pick up the value and then key off the stack and add them + // to the HashMap which is just below them on the stack. + // Leave the HashMap on the stack to pick up more key/value + // pairs if they exist. + String value = (String) objectStack.pop(); + String key = (String) objectStack.pop(); + ((HashMap) objectStack.peek()).put(key, value); + state = S_BUILD_COMMAND_ARGUMENTS; + } + } + + private void endDictionaryKey(String elementName) { + if (elementName.equals(KEY)) { + // There is a value place holder on the top of the stack and + // a key place holder just below it. + String value = (String) objectStack.pop(); + String oldKey = (String) objectStack.pop(); + String newKey = charBuffer.toString(); + if (oldKey != null && oldKey.length() != 0) { + parseProblem(NLS.bind(Messages.projRead_whichKey, oldKey, newKey)); + objectStack.push(oldKey); + } else { + objectStack.push(newKey); + } + //push back the dictionary value + objectStack.push(value); + state = S_DICTIONARY; + } + } + + private void endDictionaryValue(String elementName) { + if (elementName.equals(VALUE)) { + String newValue = charBuffer.toString(); + // There is a value place holder on the top of the stack + String oldValue = (String) objectStack.pop(); + if (oldValue != null && oldValue.length() != 0) { + parseProblem(NLS.bind(Messages.projRead_whichValue, oldValue, newValue)); + objectStack.push(oldValue); + } else { + objectStack.push(newValue); + } + state = S_DICTIONARY; + } + } + + /** + * @see ContentHandler#endElement(String, String, String) + */ + @Override + public void endElement(String uri, String elementName, String qname) { + switch (state) { + case S_PROJECT_DESC : + // Don't think we need to do anything here. + break; + case S_PROJECT_NAME : + if (elementName.equals(NAME)) { + // Project names cannot have leading/trailing whitespace + // as they are IResource names. + projectDescription.setName(charBuffer.toString().trim()); + state = S_PROJECT_DESC; + } + break; + case S_PROJECTS : + if (elementName.equals(PROJECTS)) { + endProjectsElement(elementName); + state = S_PROJECT_DESC; + } + break; + case S_DICTIONARY : + endDictionary(elementName); + break; + case S_BUILD_COMMAND_ARGUMENTS : + if (elementName.equals(ARGUMENTS)) { + // There is a hashmap on the top of the stack with the + // arguments (if any). + HashMap dictionaryArgs = (HashMap) objectStack.pop(); + state = S_BUILD_COMMAND; + if (dictionaryArgs.isEmpty()) + break; + // Below the hashMap on the stack, there is a BuildCommand. + ((BuildCommand) objectStack.peek()).setArguments(dictionaryArgs); + } + break; + case S_BUILD_COMMAND : + endBuildCommandElement(elementName); + break; + case S_BUILD_SPEC : + endBuildSpecElement(elementName); + break; + case S_BUILD_COMMAND_TRIGGERS : + endBuildTriggersElement(elementName); + break; + case S_NATURES : + endNaturesElement(elementName); + break; + case S_LINK : + endLinkElement(elementName); + break; + case S_LINKED_RESOURCES : + endLinkedResourcesElement(elementName); + break; + case S_VARIABLE : + endVariableElement(elementName); + break; + case S_FILTER : + endFilterElement(elementName); + break; + case S_FILTERED_RESOURCES : + endFilteredResourcesElement(elementName); + break; + case S_VARIABLE_LIST : + endVariableListElement(elementName); + break; + case S_PROJECT_COMMENT : + if (elementName.equals(COMMENT)) { + projectDescription.setComment(charBuffer.toString()); + state = S_PROJECT_DESC; + } + break; + case S_REFERENCED_PROJECT_NAME : + if (elementName.equals(PROJECT)) { + //top of stack is list of project references + // Referenced projects are just project names and, therefore, + // are also IResource names and cannot have leading/trailing + // whitespace. + ((ArrayList) objectStack.peek()).add(charBuffer.toString().trim()); + state = S_PROJECTS; + } + break; + case S_BUILD_COMMAND_NAME : + if (elementName.equals(NAME)) { + //top of stack is the build command + // A build command name is an extension id and + // cannot have leading/trailing whitespace. + ((BuildCommand) objectStack.peek()).setName(charBuffer.toString().trim()); + state = S_BUILD_COMMAND; + } + break; + case S_DICTIONARY_KEY : + endDictionaryKey(elementName); + break; + case S_DICTIONARY_VALUE : + endDictionaryValue(elementName); + break; + case S_NATURE_NAME : + if (elementName.equals(NATURE)) { + //top of stack is list of nature names + // A nature name is an extension id and cannot + // have leading/trailing whitespace. + ((ArrayList) objectStack.peek()).add(charBuffer.toString().trim()); + state = S_NATURES; + } + break; + case S_LINK_PATH : + endLinkPath(elementName); + break; + case S_LINK_TYPE : + endLinkType(elementName); + break; + case S_LINK_LOCATION : + endLinkLocation(elementName); + break; + case S_LINK_LOCATION_URI : + endLinkLocationURI(elementName); + break; + case S_FILTER_ID : + endFilterId(elementName); + break; + case S_FILTER_PATH : + endFilterPath(elementName); + break; + case S_FILTER_TYPE : + endFilterType(elementName); + break; + case S_MATCHER : + endMatcherElement(elementName); + break; + case S_MATCHER_ID : + endMatcherID(elementName); + break; + case S_MATCHER_ARGUMENTS : + endMatcherArguments(elementName); + break; + case S_VARIABLE_NAME : + endVariableName(elementName); + break; + case S_VARIABLE_VALUE : + endVariableValue(elementName); + break; + case S_SNAPSHOT_LOCATION : + endSnapshotLocation(elementName); + break; + } + charBuffer.setLength(0); + } + + /** + * End this group of linked resources and add them to the project description. + */ + private void endLinkedResourcesElement(String elementName) { + if (elementName.equals(LINKED_RESOURCES)) { + HashMap linkedResources = (HashMap) objectStack.pop(); + state = S_PROJECT_DESC; + if (linkedResources.isEmpty()) + return; + projectDescription.setLinkDescriptions(linkedResources); + } + } + + /** + * End this group of linked resources and add them to the project description. + */ + private void endFilteredResourcesElement(String elementName) { + if (elementName.equals(FILTERED_RESOURCES)) { + HashMap> filteredResources = (HashMap>) objectStack.pop(); + state = S_PROJECT_DESC; + if (filteredResources.isEmpty()) + return; + projectDescription.setFilterDescriptions(filteredResources); + } + } + + /** + * End this group of group resources and add them to the project + * description. + */ + private void endVariableListElement(String elementName) { + if (elementName.equals(VARIABLE_LIST)) { + HashMap variableList = (HashMap) objectStack.pop(); + state = S_PROJECT_DESC; + if (variableList.isEmpty()) + return; + projectDescription.setVariableDescriptions(variableList); + } + } + + /** + * End a single linked resource and add it to the HashMap. + */ + private void endLinkElement(String elementName) { + if (elementName.equals(LINK)) { + state = S_LINKED_RESOURCES; + // Pop off the link description + LinkDescription link = (LinkDescription) objectStack.pop(); + // Make sure that you have something reasonable + IPath path = link.getProjectRelativePath(); + int type = link.getType(); + URI location = link.getLocationURI(); + if (location == null) { + parseProblem(NLS.bind(Messages.projRead_badLinkLocation, path, Integer.toString(type))); + return; + } + if ((path == null) || path.segmentCount() == 0) { + parseProblem(NLS.bind(Messages.projRead_emptyLinkName, Integer.toString(type), location)); + return; + } + if (type == -1) { + parseProblem(NLS.bind(Messages.projRead_badLinkType, path, location)); + return; + } + + // The HashMap of linked resources is the next thing on the stack + ((HashMap) objectStack.peek()).put(link.getProjectRelativePath(), link); + } + } + + private void endMatcherElement(String elementName) { + if (elementName.equals(MATCHER)) { + // Pop off an array (Object[2]) containing the matcher id and arguments. + Object[] matcher = (Object[]) objectStack.pop(); + // Make sure that you have something reasonable + String id = (String) matcher[0]; + // the id can't be null + if (id == null) { + parseProblem(NLS.bind(Messages.projRead_badFilterID, id)); + return; + } + + if (objectStack.peek() instanceof ArrayList) { + state = S_MATCHER_ARGUMENTS; + // The ArrayList of matchers is the next thing on the stack + ArrayList list = ((ArrayList) objectStack.peek()); + list.add(new FileInfoMatcherDescription((String) matcher[0], matcher[1])); + } + + if (objectStack.peek() instanceof FilterDescription) { + state = S_FILTER; + FilterDescription d = ((FilterDescription) objectStack.peek()); + d.setFileInfoMatcherDescription(new FileInfoMatcherDescription((String) matcher[0], matcher[1])); + } + } + } + + /** + * End a single filtered resource and add it to the HashMap. + */ + private void endFilterElement(String elementName) { + if (elementName.equals(FILTER)) { + // Pop off the filter description + FilterDescription filter = (FilterDescription) objectStack.pop(); + if (project != null) { + // Make sure that you have something reasonable + IPath path = filter.getResource().getProjectRelativePath(); + int type = filter.getType(); + // arguments can be null + if (path == null) { + parseProblem(NLS.bind(Messages.projRead_emptyFilterName, Integer.toString(type))); + return; + } + if (type == -1) { + parseProblem(NLS.bind(Messages.projRead_badFilterType, path)); + return; + } + + // The HashMap of filtered resources is the next thing on the stack + HashMap> map = ((HashMap>) objectStack.peek()); + LinkedList list = map.get(filter.getResource().getProjectRelativePath()); + if (list == null) { + list = new LinkedList<>(); + map.put(filter.getResource().getProjectRelativePath(), list); + } + list.add(filter); + } else { + // if the project is null, that means that we're loading a project description to retrieve + // some meta data only. + String key = ""; // an empty key; //$NON-NLS-1$ + HashMap> map = ((HashMap>) objectStack.peek()); + LinkedList list = map.get(key); + if (list == null) { + list = new LinkedList<>(); + map.put(key, list); + } + list.add(filter); + } + state = S_FILTERED_RESOURCES; + } + } + + /** + * End a single group resource and add it to the HashMap. + */ + private void endVariableElement(String elementName) { + if (elementName.equals(VARIABLE)) { + state = S_VARIABLE_LIST; + // Pop off the link description + VariableDescription desc = (VariableDescription) objectStack.pop(); + // Make sure that you have something reasonable + if (desc.getName().length() == 0) { + parseProblem(NLS.bind(Messages.projRead_emptyVariableName, project.getName())); + return; + } + + // The HashMap of variables is the next thing on the stack + ((HashMap) objectStack.peek()).put(desc.getName(), desc); + } + } + + /** + * For backwards compatibility, link locations in the local file system are represented + * in the project description under the "location" tag. + * @param elementName + */ + private void endLinkLocation(String elementName) { + if (elementName.equals(LOCATION)) { + // A link location is an URI. URIs cannot have leading/trailing whitespace + String newLocation = charBuffer.toString().trim(); + // objectStack has a LinkDescription on it. Set the type on this LinkDescription. + URI oldLocation = ((LinkDescription) objectStack.peek()).getLocationURI(); + if (oldLocation != null) { + parseProblem(NLS.bind(Messages.projRead_badLocation, oldLocation, newLocation)); + } else { + ((LinkDescription) objectStack.peek()).setLocationURI(URIUtil.toURI(Path.fromPortableString(newLocation))); + } + state = S_LINK; + } + } + + /** + * Link locations that are not stored in the local file system are represented + * in the project description under the "locationURI" tag. + * @param elementName + */ + private void endLinkLocationURI(String elementName) { + if (elementName.equals(LOCATION_URI)) { + // A link location is an URI. URIs cannot have leading/trailing whitespace + String newLocation = charBuffer.toString().trim(); + // objectStack has a LinkDescription on it. Set the type on this LinkDescription. + URI oldLocation = ((LinkDescription) objectStack.peek()).getLocationURI(); + if (oldLocation != null) { + parseProblem(NLS.bind(Messages.projRead_badLocation, oldLocation, newLocation)); + } else { + try { + ((LinkDescription) objectStack.peek()).setLocationURI(new URI(newLocation)); + } catch (URISyntaxException e) { + String msg = Messages.projRead_failureReadingProjectDesc; + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, msg, e)); + } + } + state = S_LINK; + } + } + + private void endLinkPath(String elementName) { + if (elementName.equals(NAME)) { + IPath newPath = new Path(charBuffer.toString()); + // objectStack has a LinkDescription on it. Set the name + // on this LinkDescription. + IPath oldPath = ((LinkDescription) objectStack.peek()).getProjectRelativePath(); + if (oldPath.segmentCount() != 0) { + parseProblem(NLS.bind(Messages.projRead_badLinkName, oldPath, newPath)); + } else { + ((LinkDescription) objectStack.peek()).setPath(newPath); + } + state = S_LINK; + } + } + + private void endMatcherID(String elementName) { + if (elementName.equals(ID)) { + // The matcher id is String. + String newID = charBuffer.toString().trim(); + // objectStack has an array (Object[2]) on it for the matcher id and arguments. + String oldID = (String) ((Object[]) objectStack.peek())[0]; + if (oldID != null) { + parseProblem(NLS.bind(Messages.projRead_badID, oldID, newID)); + } else { + ((Object[]) objectStack.peek())[0] = newID; + } + state = S_MATCHER; + } + } + + private void endMatcherArguments(String elementName) { + if (elementName.equals(ARGUMENTS)) { + ArrayList matchers = (ArrayList) objectStack.pop(); + Object newArguments = charBuffer.toString(); + + if (matchers.size() > 0) + newArguments = matchers.toArray(new FileInfoMatcherDescription[matchers.size()]); + + // objectStack has an array (Object[2]) on it for the matcher id and arguments. + String oldArguments = (String) ((Object[]) objectStack.peek())[1]; + if (oldArguments != null) { + parseProblem(NLS.bind(Messages.projRead_badArguments, oldArguments, newArguments)); + } else + ((Object[]) objectStack.peek())[1] = newArguments; + state = S_MATCHER; + } + } + + private void endFilterId(String elementName) { + if (elementName.equals(ID)) { + Long newId = Long.parseLong(charBuffer.toString()); + // objectStack has a FilterDescription on it. Set the name + // on this FilterDescription. + long oldId = ((FilterDescription) objectStack.peek()).getId(); + if (oldId != 0) { + parseProblem(NLS.bind(Messages.projRead_badFilterName, oldId, newId)); + } else { + ((FilterDescription) objectStack.peek()).setId(newId.longValue()); + } + state = S_FILTER; + } + } + + private void endFilterPath(String elementName) { + if (elementName.equals(NAME)) { + IPath newPath = new Path(charBuffer.toString()); + // objectStack has a FilterDescription on it. Set the name + // on this FilterDescription. + IResource oldResource = ((FilterDescription) objectStack.peek()).getResource(); + if (oldResource != null) { + parseProblem(NLS.bind(Messages.projRead_badFilterName, oldResource.getProjectRelativePath(), newPath)); + } else { + if (project != null) { + ((FilterDescription) objectStack.peek()).setResource(newPath.isEmpty() ? (IResource) project : project.getFolder(newPath)); + } else { + // if the project is null, that means that we're loading a project description to retrieve + // some meta data only. + ((FilterDescription) objectStack.peek()).setResource(null); + } + } + state = S_FILTER; + } + } + + private void endFilterType(String elementName) { + if (elementName.equals(TYPE)) { + int newType = -1; + try { + // parseInt expects a string containing only numerics + // or a leading '-'. Ensure there is no leading/trailing + // whitespace. + newType = Integer.parseInt(charBuffer.toString().trim()); + } catch (NumberFormatException e) { + log(e); + } + // objectStack has a FilterDescription on it. Set the type + // on this FilterDescription. + int oldType = ((FilterDescription) objectStack.peek()).getType(); + if (oldType != -1) { + parseProblem(NLS.bind(Messages.projRead_badFilterType2, Integer.toString(oldType), Integer.toString(newType))); + } else { + ((FilterDescription) objectStack.peek()).setType(newType); + } + state = S_FILTER; + } + } + + private void endVariableName(String elementName) { + if (elementName.equals(NAME)) { + String value = charBuffer.toString(); + // objectStack has a VariableDescription on it. Set the value + // on this ValueDescription. + ((VariableDescription) objectStack.peek()).setName(value); + state = S_VARIABLE; + } + } + + private void endVariableValue(String elementName) { + if (elementName.equals(VALUE)) { + String value = charBuffer.toString(); + // objectStack has a VariableDescription on it. Set the value + // on this ValueDescription. + ((VariableDescription) objectStack.peek()).setValue(value); + state = S_VARIABLE; + } + } + + private void endLinkType(String elementName) { + if (elementName.equals(TYPE)) { + //FIXME we should handle this case by removing the entire link + //for now we default to a file link + int newType = IResource.FILE; + try { + // parseInt expects a string containing only numerics + // or a leading '-'. Ensure there is no leading/trailing + // whitespace. + newType = Integer.parseInt(charBuffer.toString().trim()); + } catch (NumberFormatException e) { + log(e); + } + // objectStack has a LinkDescription on it. Set the type + // on this LinkDescription. + int oldType = ((LinkDescription) objectStack.peek()).getType(); + if (oldType != -1) { + parseProblem(NLS.bind(Messages.projRead_badLinkType2, Integer.toString(oldType), Integer.toString(newType))); + } else { + ((LinkDescription) objectStack.peek()).setType(newType); + } + state = S_LINK; + } + } + + /** + * End of an element that is part of a nature list + */ + private void endNaturesElement(String elementName) { + if (elementName.equals(NATURES)) { + // Pop the array list of natures off the stack + ArrayList natures = (ArrayList) objectStack.pop(); + state = S_PROJECT_DESC; + if (natures.size() == 0) + return; + String[] natureNames = natures.toArray(new String[natures.size()]); + projectDescription.setNatureIds(natureNames); + } + } + + /** + * End of an element that is part of a project references list + */ + private void endProjectsElement(String elementName) { + // Pop the array list that contains all the referenced project names + ArrayList referencedProjects = (ArrayList) objectStack.pop(); + if (referencedProjects.size() == 0) + // Don't bother adding an empty group of referenced projects to the + // project descriptor. + return; + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IProject[] projects = new IProject[referencedProjects.size()]; + for (int i = 0; i < projects.length; i++) { + projects[i] = root.getProject(referencedProjects.get(i)); + } + projectDescription.setReferencedProjects(projects); + } + + private void endSnapshotLocation(String elementName) { + if (elementName.equals(SNAPSHOT_LOCATION)) { + String location = charBuffer.toString().trim(); + try { + projectDescription.setSnapshotLocationURI(new URI(location)); + } catch (URISyntaxException e) { + String msg = NLS.bind(Messages.projRead_badSnapshotLocation, location); + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, msg, e)); + } + state = S_PROJECT_DESC; + } + } + + /** + * @see ErrorHandler#error(SAXParseException) + */ + @Override + public void error(SAXParseException error) { + log(error); + } + + /** + * @see ErrorHandler#fatalError(SAXParseException) + */ + @Override + public void fatalError(SAXParseException error) throws SAXException { + // ensure a null value is not passed as message to Status constructor (bug 42782) + String message = error.getMessage(); + if (project != null) + message = NLS.bind(Messages.resources_readMeta, project.getName()); + problems.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message == null ? "" : message, error)); //$NON-NLS-1$ + throw error; + } + + protected void log(Exception ex) { + String message = ex.getMessage(); + if (project != null) + message = NLS.bind(Messages.resources_readMeta, project.getName()); + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message == null ? "" : message, ex)); //$NON-NLS-1$ + } + + private void parseProblem(String errorMessage) { + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, errorMessage, null)); + } + + private void parseProjectDescription(String elementName) { + if (elementName.equals(NAME)) { + state = S_PROJECT_NAME; + return; + } + if (elementName.equals(COMMENT)) { + state = S_PROJECT_COMMENT; + return; + } + if (elementName.equals(PROJECTS)) { + state = S_PROJECTS; + // Push an array list on the object stack to hold the name + // of all the referenced projects. This array list will be + // popped off the stack, massaged into the right format + // and added to the project description when we hit the + // end element for PROJECTS. + objectStack.push(new ArrayList()); + return; + } + if (elementName.equals(BUILD_SPEC)) { + state = S_BUILD_SPEC; + // Push an array list on the object stack to hold the build commands + // for this build spec. This array list will be popped off the stack, + // massaged into the right format and added to the project's build + // spec when we hit the end element for BUILD_SPEC. + objectStack.push(new ArrayList()); + return; + } + if (elementName.equals(NATURES)) { + state = S_NATURES; + // Push an array list to hold all the nature names. + objectStack.push(new ArrayList()); + return; + } + if (elementName.equals(LINKED_RESOURCES)) { + // Push a HashMap to collect all the links. + objectStack.push(new HashMap()); + state = S_LINKED_RESOURCES; + return; + } + if (elementName.equals(FILTERED_RESOURCES)) { + // Push a HashMap to collect all the filters. + objectStack.push(new HashMap>()); + state = S_FILTERED_RESOURCES; + return; + } + if (elementName.equals(VARIABLE_LIST)) { + // Push a HashMap to collect all the variables. + objectStack.push(new HashMap()); + state = S_VARIABLE_LIST; + return; + } + if (elementName.equals(SNAPSHOT_LOCATION)) { + state = S_SNAPSHOT_LOCATION; + return; + } + } + + public ProjectDescription read(InputSource input) { + problems = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, Messages.projRead_failureReadingProjectDesc, null); + objectStack = new Stack<>(); + state = S_INITIAL; + try { + createParser().parse(input, this); + } catch (ParserConfigurationException e) { + log(e); + } catch (IOException e) { + log(e); + } catch (SAXException e) { + log(e); + } + + if (projectDescription != null && projectDescription.getName() == null) + parseProblem(Messages.projRead_missingProjectName); + + switch (problems.getSeverity()) { + case IStatus.ERROR : + Policy.log(problems); + return null; + case IStatus.WARNING : + case IStatus.INFO : + Policy.log(problems); + case IStatus.OK : + default : + return projectDescription; + } + } + + /** + * Reads and returns a project description stored at the given location + */ + public ProjectDescription read(IPath location) throws IOException { + BufferedInputStream file = null; + try { + file = new BufferedInputStream(new FileInputStream(location.toFile())); + return read(new InputSource(file)); + } finally { + FileUtil.safeClose(file); + } + } + + /** + * Reads and returns a project description stored at the given location, or + * temporary location. + */ + public ProjectDescription read(IPath location, IPath tempLocation) throws IOException { + SafeFileInputStream file = new SafeFileInputStream(location.toOSString(), tempLocation.toOSString()); + try { + return read(new InputSource(file)); + } finally { + file.close(); + } + } + + /** + * @see ContentHandler#startElement(String, String, String, Attributes) + */ + @Override + public void startElement(String uri, String elementName, String qname, Attributes attributes) throws SAXException { + //clear the character buffer at the start of every element + charBuffer.setLength(0); + switch (state) { + case S_INITIAL : + if (elementName.equals(PROJECT_DESCRIPTION)) { + state = S_PROJECT_DESC; + projectDescription = new ProjectDescription(); + } else { + throw (new SAXException(NLS.bind(Messages.projRead_notProjectDescription, elementName))); + } + break; + case S_PROJECT_DESC : + parseProjectDescription(elementName); + break; + case S_PROJECTS : + if (elementName.equals(PROJECT)) { + state = S_REFERENCED_PROJECT_NAME; + } + break; + case S_BUILD_SPEC : + if (elementName.equals(BUILD_COMMAND)) { + state = S_BUILD_COMMAND; + objectStack.push(new BuildCommand()); + } + break; + case S_BUILD_COMMAND : + if (elementName.equals(NAME)) { + state = S_BUILD_COMMAND_NAME; + } else if (elementName.equals(BUILD_TRIGGERS)) { + state = S_BUILD_COMMAND_TRIGGERS; + } else if (elementName.equals(ARGUMENTS)) { + state = S_BUILD_COMMAND_ARGUMENTS; + // Push a HashMap to hold all the key/value pairs which + // will become the argument list. + objectStack.push(new HashMap()); + } + break; + case S_BUILD_COMMAND_ARGUMENTS : + if (elementName.equals(DICTIONARY)) { + state = S_DICTIONARY; + // Push 2 strings for the key/value pair to be read + objectStack.push(""); // key //$NON-NLS-1$ + objectStack.push(""); // value //$NON-NLS-1$ + } + break; + case S_DICTIONARY : + if (elementName.equals(KEY)) { + state = S_DICTIONARY_KEY; + } else if (elementName.equals(VALUE)) { + state = S_DICTIONARY_VALUE; + } + break; + case S_NATURES : + if (elementName.equals(NATURE)) { + state = S_NATURE_NAME; + } + break; + case S_LINKED_RESOURCES : + if (elementName.equals(LINK)) { + state = S_LINK; + // Push place holders for the name, type and location of + // this link. + objectStack.push(new LinkDescription()); + } + break; + case S_VARIABLE_LIST : + if (elementName.equals(VARIABLE)) { + state = S_VARIABLE; + // Push place holders for the name, type and location of + // this link. + objectStack.push(new VariableDescription()); + } + break; + case S_LINK : + if (elementName.equals(NAME)) { + state = S_LINK_PATH; + } else if (elementName.equals(TYPE)) { + state = S_LINK_TYPE; + } else if (elementName.equals(LOCATION)) { + state = S_LINK_LOCATION; + } else if (elementName.equals(LOCATION_URI)) { + state = S_LINK_LOCATION_URI; + } + break; + case S_FILTERED_RESOURCES : + if (elementName.equals(FILTER)) { + state = S_FILTER; + // Push place holders for the name, type, id and arguments of + // this filter. + objectStack.push(new FilterDescription()); + } + break; + case S_FILTER : + if (elementName.equals(ID)) { + state = S_FILTER_ID; + } else if (elementName.equals(NAME)) { + state = S_FILTER_PATH; + } else if (elementName.equals(TYPE)) { + state = S_FILTER_TYPE; + } else if (elementName.equals(MATCHER)) { + state = S_MATCHER; + // Push an array for the matcher id and arguments + objectStack.push(new Object[2]); + } + break; + case S_MATCHER : + if (elementName.equals(ID)) { + state = S_MATCHER_ID; + } else if (elementName.equals(ARGUMENTS)) { + state = S_MATCHER_ARGUMENTS; + objectStack.push(new ArrayList()); + } + break; + case S_MATCHER_ARGUMENTS : + if (elementName.equals(MATCHER)) { + state = S_MATCHER; + // Push an array for the matcher id and arguments + objectStack.push(new Object[2]); + } + break; + case S_VARIABLE : + if (elementName.equals(NAME)) { + state = S_VARIABLE_NAME; + } else if (elementName.equals(VALUE)) { + state = S_VARIABLE_VALUE; + } + break; + } + } + + /** + * @see ErrorHandler#warning(SAXParseException) + */ + @Override + public void warning(SAXParseException error) { + log(error); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java new file mode 100644 index 0000000000..7b7151e6a8 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.HashMap; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.resources.ICommand; +import org.eclipse.core.resources.IProjectNature; +import org.eclipse.core.runtime.content.IContentTypeMatcher; + +public class ProjectInfo extends ResourceInfo { + + /** The description of this object */ + protected ProjectDescription description = null; + + /** The list of natures for this project */ + protected HashMap natures = null; + + /** The property store for this resource (used only by the compatibility fragment) */ + protected Object propertyStore = null; + + /** The content type matcher for this project. */ + protected IContentTypeMatcher matcher = null; + + /** + * Discards stale natures on this project after project description + * has changed. + */ + public synchronized void discardNatures() { + natures = null; + } + + /** + * Discards any stale state on this project after it has been moved. Builder + * instances must be cleared because they reference the old project handle. + */ + public synchronized void fixupAfterMove() { + natures = null; + // note that the property store instance will be recreated lazily + propertyStore = null; + if (description != null) { + ICommand[] buildSpec = description.getBuildSpec(false); + for (int i = 0; i < buildSpec.length; i++) + ((BuildCommand) buildSpec[i]).setBuilders(null); + } + } + + /** + * Returns the description associated with this info. The return value may be null. + */ + public ProjectDescription getDescription() { + return description; + } + + /** + * Returns the content type matcher associated with this info. The return value may be null. + */ + public IContentTypeMatcher getMatcher() { + return matcher; + } + + public IProjectNature getNature(String natureId) { + // thread safety: (Concurrency001) + HashMap temp = natures; + if (temp == null) + return null; + return temp.get(natureId); + } + + /** + * Returns the property store associated with this info. The return value may be null. + */ + @Override + public Object getPropertyStore() { + return propertyStore; + } + + /** + * Sets the description associated with this info. The value may be null. + */ + public void setDescription(ProjectDescription value) { + if (description != null) { + //if we already have a description, assign the new + //build spec on top of the old one to ensure we maintain + //any existing builder instances in the old build commands + ICommand[] oldSpec = description.buildSpec; + ICommand[] newSpec = value.buildSpec; + value.buildSpec = oldSpec; + value.setBuildSpec(newSpec); + } + description = value; + } + + /** + * Sets the content type matcher to be associated with this info. The value may be null. + */ + public void setMatcher(IContentTypeMatcher matcher) { + this.matcher = matcher; + } + + @SuppressWarnings({"unchecked"}) + public synchronized void setNature(String natureId, IProjectNature value) { + // thread safety: (Concurrency001) + if (value == null) { + if (natures == null) + return; + HashMap temp = (HashMap) natures.clone(); + temp.remove(natureId); + if (temp.isEmpty()) + natures = null; + else + natures = temp; + } else { + HashMap temp = natures; + if (temp == null) + temp = new HashMap<>(5); + else + temp = (HashMap) natures.clone(); + temp.put(natureId, value); + natures = temp; + } + } + + /** + * Sets the property store associated with this info. The value may be null. + */ + @Override + public void setPropertyStore(Object value) { + propertyStore = value; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java new file mode 100644 index 0000000000..662a72f616 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.ArrayList; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IProjectNatureDescriptor; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + */ +public class ProjectNatureDescriptor implements IProjectNatureDescriptor { + protected String id; + protected String label; + protected String[] requiredNatures; + protected String[] natureSets; + protected String[] builderIds; + protected String[] contentTypeIds; + protected boolean allowLinking = true; + + //descriptors that are in a dependency cycle are never valid + protected boolean hasCycle = false; + //colours used by cycle detection algorithm + protected byte colour = 0; + + /** + * Creates a new descriptor based on the given extension markup. + * @exception CoreException if the given nature extension is not correctly formed. + */ + protected ProjectNatureDescriptor(IExtension natureExtension) throws CoreException { + readExtension(natureExtension); + } + + protected void fail() throws CoreException { + fail(NLS.bind(Messages.natures_invalidDefinition, id)); + } + + protected void fail(String reason) throws CoreException { + throw new ResourceException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, reason, null)); + } + + /** + * Returns the IDs of the incremental builders that this nature claims to + * own. These builders do not necessarily exist in the registry. + */ + public String[] getBuilderIds() { + return builderIds; + } + + /** + * Returns the IDs of the content types this nature declares to + * have affinity with. These content types do not necessarily exist in the registry. + */ + public String[] getContentTypeIds() { + return contentTypeIds; + } + + /** + * @see IProjectNatureDescriptor#getNatureId() + */ + @Override + public String getNatureId() { + return id; + } + + /** + * @see IProjectNatureDescriptor#getLabel() + */ + @Override + public String getLabel() { + return label; + } + + /** + * @see IProjectNatureDescriptor#getRequiredNatureIds() + */ + @Override + public String[] getRequiredNatureIds() { + return requiredNatures; + } + + /** + * @see IProjectNatureDescriptor#getNatureSetIds() + */ + @Override + public String[] getNatureSetIds() { + return natureSets; + } + + /** + * @see IProjectNatureDescriptor#isLinkingAllowed() + */ + @Override + public boolean isLinkingAllowed() { + return allowLinking; + } + + /** + * Initialize this nature descriptor based on the provided extension point. + */ + protected void readExtension(IExtension natureExtension) throws CoreException { + //read the extension + id = natureExtension.getUniqueIdentifier(); + if (id == null) { + fail(Messages.natures_missingIdentifier); + } + label = natureExtension.getLabel(); + IConfigurationElement[] elements = natureExtension.getConfigurationElements(); + int count = elements.length; + ArrayList requiredList = new ArrayList<>(count); + ArrayList setList = new ArrayList<>(count); + ArrayList builderList = new ArrayList<>(count); + ArrayList contentTypeList = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + IConfigurationElement element = elements[i]; + String name = element.getName(); + if (name.equalsIgnoreCase("requires-nature")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + requiredList.add(attribute); + } else if (name.equalsIgnoreCase("one-of-nature")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + setList.add(attribute); + } else if (name.equalsIgnoreCase("builder")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + builderList.add(attribute); + } else if (name.equalsIgnoreCase("content-type")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + contentTypeList.add(attribute); + } else if (name.equalsIgnoreCase("options")) { //$NON-NLS-1$ + String attribute = element.getAttribute("allowLinking"); //$NON-NLS-1$ + //when in doubt (missing attribute, wrong value) default to allow linking + allowLinking = !Boolean.FALSE.toString().equalsIgnoreCase(attribute); + } + } + requiredNatures = requiredList.toArray(new String[requiredList.size()]); + natureSets = setList.toArray(new String[setList.size()]); + builderIds = builderList.toArray(new String[builderList.size()]); + contentTypeIds = contentTypeList.toArray(new String[contentTypeList.size()]); + } + + /** + * Prints out a string representation for debugging purposes only. + */ + @Override + public String toString() { + return "ProjectNatureDescriptor(" + id + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java new file mode 100644 index 0000000000..7915c3daa5 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java @@ -0,0 +1,503 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import org.eclipse.core.filesystem.URIUtil; +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; + +/** + * The {@link IPathVariableManager} for a single project + * @see IProject#getPathVariableManager() + */ +public class ProjectPathVariableManager implements IPathVariableManager, IManager { + + private Resource resource; + private ProjectVariableProviderManager.Descriptor variableProviders[] = null; + + /** + * Constructor for the class. + */ + public ProjectPathVariableManager(Resource resource) { + this.resource = resource; + variableProviders = ProjectVariableProviderManager.getDefault().getDescriptors(); + } + + PathVariableManager getWorkspaceManager() { + return (PathVariableManager) resource.getWorkspace().getPathVariableManager(); + } + + /** + * Throws a runtime exception if the given name is not valid as a path + * variable name. + */ + private void checkIsValidName(String name) throws CoreException { + IStatus status = validateName(name); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * Throws an exception if the given path is not valid as a path variable + * value. + */ + private void checkIsValidValue(URI newValue) throws CoreException { + IStatus status = validateValue(newValue); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#getPathVariableNames() + */ + @Override + public String[] getPathVariableNames() { + List result = new LinkedList<>(); + HashMap map; + try { + map = ((ProjectDescription) resource.getProject().getDescription()).getVariables(); + } catch (CoreException e) { + return new String[0]; + } + for (int i = 0; i < variableProviders.length; i++) { + String[] variableHints = variableProviders[i].getVariableNames(variableProviders[i].getName(), resource); + if (variableHints != null && variableHints.length > 0) + for (int k = 0; k < variableHints.length; k++) + result.add(variableProviders[i].getVariableNames(variableProviders[i].getName(), resource)[k]); + } + if (map != null) + result.addAll(map.keySet()); + result.addAll(Arrays.asList(getWorkspaceManager().getPathVariableNames())); + return result.toArray(new String[0]); + } + + /** + * @deprecated use {@link #getURIValue(String)} instead. + */ + @Deprecated + @Override + public IPath getValue(String varName) { + URI uri = getURIValue(varName); + if (uri != null) + return URIUtil.toPath(uri); + return null; + } + + /** + * If the variable is not listed in the project description, we fall back on + * the workspace variables. + * + * @see org.eclipse.core.resources.IPathVariableManager#getURIValue(String) + */ + @Override + public URI getURIValue(String varName) { + String value = internalGetValue(varName); + if (value != null) { + if (value.indexOf("..") != -1) { //$NON-NLS-1$ + // if the path is 'reducible', lets resolve it first. + int index = value.indexOf(IPath.SEPARATOR); + if (index > 0) { // if its the first character, its an + // absolute path on unix, so we don't + // resolve it + URI resolved = resolveVariable(value); + if (resolved != null) + return resolved; + } + } + try { + return URI.create(value); + } catch (IllegalArgumentException e) { + IPath path = Path.fromPortableString(value); + return URIUtil.toURI(path); + } + } + return getWorkspaceManager().getURIValue(varName); + } + + public String internalGetValue(String varName) { + HashMap map; + try { + map = ((ProjectDescription) resource.getProject().getDescription()).getVariables(); + } catch (CoreException e) { + return null; + } + if (map != null && map.containsKey(varName)) + return map.get(varName).getValue(); + + String name; + int index = varName.indexOf('-'); + if (index != -1) + name = varName.substring(0, index); + else + name = varName; + for (int i = 0; i < variableProviders.length; i++) { + if (variableProviders[i].getName().equals(name)) + return variableProviders[i].getValue(varName, resource); + } + for (int i = 0; i < variableProviders.length; i++) { + if (name.startsWith(variableProviders[i].getName())) + return variableProviders[i].getValue(varName, resource); + } + return null; + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#isDefined(String) + */ + @Override + public boolean isDefined(String varName) { + for (int i = 0; i < variableProviders.length; i++) { + // if (variableProviders[i].getName().equals(varName)) + // return true; + + if (varName.startsWith(variableProviders[i].getName())) + return true; + } + + try { + HashMap map = ((ProjectDescription) resource.getProject().getDescription()).getVariables(); + if (map != null) { + Iterator it = map.keySet().iterator(); + while (it.hasNext()) { + String name = it.next(); + if (name.equals(varName)) + return true; + } + } + } catch (CoreException e) { + return false; + } + boolean value = getWorkspaceManager().isDefined(varName); + if (!value) { + // this is to handle variables with encoded arguments + int index = varName.indexOf('-'); + if (index != -1) { + String newVarName = varName.substring(0, index); + value = isDefined(newVarName); + } + } + return value; + } + + /** + * @deprecated use {@link #resolveURI(URI)} instead. + */ + @Override + @Deprecated + public IPath resolvePath(IPath path) { + if (path == null || path.segmentCount() == 0 || path.isAbsolute() || path.getDevice() != null) + return path; + URI value = resolveURI(URIUtil.toURI(path)); + return value == null ? path : URIUtil.toPath(value); + } + + public URI resolveVariable(String variable) { + LinkedList variableStack = new LinkedList<>(); + + String value = resolveVariable(variable, variableStack); + if (value != null) { + try { + return URI.create(value); + } catch (IllegalArgumentException e) { + return URIUtil.toURI(Path.fromPortableString(value)); + } + } + return null; + } + + public String resolveVariable(String value, LinkedList variableStack) { + if (variableStack == null) + variableStack = new LinkedList<>(); + + String tmp = internalGetValue(value); + if (tmp == null) { + URI result = getWorkspaceManager().getURIValue(value); + if (result != null) + return result.toASCIIString(); + } else + value = tmp; + + while (true) { + String stringValue; + try { + URI uri = URI.create(value); + if (uri != null) { + IPath path = URIUtil.toPath(uri); + if (path != null) + stringValue = path.toPortableString(); + else + stringValue = value; + } else + stringValue = value; + } catch (IllegalArgumentException e) { + stringValue = value; + } + // we check if the value contains referenced variables with ${VAR} + int index = stringValue.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = PathVariableUtil.getMatchingBrace(stringValue, index); + String macro = stringValue.substring(index + 2, endIndex); + String resolvedMacro = ""; //$NON-NLS-1$ + if (!variableStack.contains(macro)) { + variableStack.add(macro); + resolvedMacro = resolveVariable(macro, variableStack); + if (resolvedMacro == null) + resolvedMacro = ""; //$NON-NLS-1$ + } + if (stringValue.length() > endIndex) + stringValue = stringValue.substring(0, index) + resolvedMacro + stringValue.substring(endIndex + 1); + else + stringValue = resolvedMacro; + value = stringValue; + } else + break; + } + return value; + } + + @Override + public URI resolveURI(URI uri) { + if (uri == null || uri.isAbsolute() || (uri.getSchemeSpecificPart() == null)) + return uri; + IPath raw = new Path(uri.getSchemeSpecificPart()); + if (raw == null || raw.segmentCount() == 0 || raw.isAbsolute() || raw.getDevice() != null) + return URIUtil.toURI(raw); + URI value = resolveVariable(raw.segment(0)); + if (value == null) + return uri; + + String path = value.getPath(); + if (path != null) { + IPath p = Path.fromPortableString(path); + p = p.append(raw.removeFirstSegments(1)); + try { + value = new URI(value.getScheme(), value.getHost(), p.toPortableString(), value.getFragment()); + } catch (URISyntaxException e) { + return uri; + } + return value; + } + return uri; + } + + /** + * @deprecated use {@link #setURIValue(String, URI)} instead. + */ + @Deprecated + @Override + public void setValue(String varName, IPath newValue) throws CoreException { + if (newValue == null) + setURIValue(varName, (URI) null); + else + setURIValue(varName, URIUtil.toURI(newValue)); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#setValue(String, + * IPath) + */ + @Override + public void setURIValue(String varName, URI newValue) throws CoreException { + checkIsValidName(varName); + checkIsValidValue(newValue); + // read previous value and set new value atomically in order to generate + // the right event + boolean changeWorkspaceValue = false; + Project project = (Project) resource.getProject(); + int eventType = 0; + synchronized (this) { + String value = internalGetValue(varName); + URI currentValue = null; + if (value == null) + currentValue = getWorkspaceManager().getURIValue(varName); + else { + try { + currentValue = URI.create(value); + } catch (IllegalArgumentException e) { + currentValue = null; + } + } + boolean variableExists = currentValue != null; + if (!variableExists && newValue == null) + return; + if (variableExists && currentValue.equals(newValue)) + return; + + for (int i = 0; i < variableProviders.length; i++) { + // if (variableProviders[i].getName().equals(varName)) + // return; + + if (varName.startsWith(variableProviders[i].getName())) + return; + } + + if (value == null && variableExists) + changeWorkspaceValue = true; + else { + IProgressMonitor monitor = new NullProgressMonitor(); + final ISchedulingRule rule = resource.getProject(); // project.workspace.getRuleFactory().modifyRule(project); + try { + project.workspace.prepareOperation(rule, monitor); + project.workspace.beginOperation(true); + // save the location in the project description + ProjectDescription description = project.internalGetDescription(); + if (newValue == null) { + description.setVariableDescription(varName, null); + eventType = IPathVariableChangeEvent.VARIABLE_DELETED; + } else { + description.setVariableDescription(varName, new VariableDescription(varName, newValue.toASCIIString())); + eventType = variableExists ? IPathVariableChangeEvent.VARIABLE_CHANGED : IPathVariableChangeEvent.VARIABLE_CREATED; + } + project.writeDescription(IResource.NONE); + } finally { + project.workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } + } + if (changeWorkspaceValue) + getWorkspaceManager().setURIValue(varName, newValue); + else { + // notify listeners from outside the synchronized block to avoid deadlocks + getWorkspaceManager().fireVariableChangeEvent(project, varName, newValue != null ? URIUtil.toPath(newValue) : null, eventType); + } + } + + /** + * @see org.eclipse.core.internal.resources.IManager#shutdown(IProgressMonitor) + */ + @Override + public void shutdown(IProgressMonitor monitor) { + // nothing to do here + } + + /** + * @see org.eclipse.core.internal.resources.IManager#startup(IProgressMonitor) + */ + @Override + public void startup(IProgressMonitor monitor) { + // nothing to do here + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#validateName(String) + */ + @Override + public IStatus validateName(String name) { + String message = null; + if (name.length() == 0) { + message = Messages.pathvar_length; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + char first = name.charAt(0); + if (!Character.isLetter(first) && first != '_') { + message = NLS.bind(Messages.pathvar_beginLetter, String.valueOf(first)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + for (int i = 1; i < name.length(); i++) { + char following = name.charAt(i); + if (Character.isWhitespace(following)) + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, Messages.pathvar_whitespace); + if (!Character.isLetter(following) && !Character.isDigit(following) && following != '_') { + message = NLS.bind(Messages.pathvar_invalidChar, String.valueOf(following)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + } + // check + + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#validateValue(IPath) + */ + @Override + public IStatus validateValue(IPath value) { + // accept any format + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#validateValue(URI) + */ + @Override + public IStatus validateValue(URI value) { + // accept any format + return Status.OK_STATUS; + } + + /** + * @throws CoreException + * @see IPathVariableManager#convertToRelative(URI, boolean, String) + */ + @Override + public URI convertToRelative(URI path, boolean force, String variableHint) throws CoreException { + return PathVariableUtil.convertToRelative(this, path, resource, force, variableHint); + } + + /** + * @see IPathVariableManager#convertToUserEditableFormat(String, boolean) + */ + @Override + public String convertToUserEditableFormat(String value, boolean locationFormat) { + return PathVariableUtil.convertToUserEditableFormatInternal(value, locationFormat); + } + + @Override + public String convertFromUserEditableFormat(String userFormat, boolean locationFormat) { + return PathVariableUtil.convertFromUserEditableFormatInternal(this, userFormat, locationFormat); + } + + @Override + public void addChangeListener(IPathVariableChangeListener listener) { + getWorkspaceManager().addChangeListener(listener, resource.getProject()); + } + + @Override + public void removeChangeListener(IPathVariableChangeListener listener) { + getWorkspaceManager().removeChangeListener(listener, resource.getProject()); + } + + @Override + public URI getVariableRelativePathLocation(URI location) { + try { + URI result = convertToRelative(location, false, null); + if (!result.equals(location)) + return result; + } catch (CoreException e) { + // handled by returning null + } + return null; + } + + /* + * Return the resource of this manager. + */ + public IResource getResource() { + return resource; + } + + @Override + public boolean isUserDefined(String name) { + return ProjectVariableProviderManager.getDefault().findDescriptor(name) == null; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java new file mode 100644 index 0000000000..8ffb91aeb1 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java @@ -0,0 +1,689 @@ +/******************************************************************************* + * 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 + * Markus Schorn (Wind River) - [108066] Project prefs marked dirty on read + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427, 483529 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.*; +import org.eclipse.core.internal.preferences.*; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IExportedPreferences; +import org.eclipse.osgi.util.NLS; +import org.osgi.service.prefs.BackingStoreException; +import org.osgi.service.prefs.Preferences; + +/** + * Represents a node in the Eclipse preference hierarchy which stores preference + * values for projects. + * + * @since 3.0 + */ +public class ProjectPreferences extends EclipsePreferences { + static final String PREFS_REGULAR_QUALIFIER = ResourcesPlugin.PI_RESOURCES; + static final String PREFS_DERIVED_QUALIFIER = PREFS_REGULAR_QUALIFIER + ".derived"; //$NON-NLS-1$ + /** + * Cache which nodes have been loaded from disk + */ + protected static Set loadedNodes = Collections.synchronizedSet(new HashSet()); + private IFile file; + private boolean initialized = false; + /** + * Flag indicating that this node is currently reading values from disk, + * to avoid flushing during a read. + */ + private boolean isReading; + /** + * Flag indicating that this node is currently writing values to disk, + * to avoid re-reading after the write completes. + */ + private boolean isWriting; + private IEclipsePreferences loadLevel; + private IProject project; + private String qualifier; + + // cache + private int segmentCount; + + static void deleted(IFile file) throws CoreException { + IPath path = file.getFullPath(); + int count = path.segmentCount(); + if (count != 3) + return; + // check if we are in the .settings directory + if (!EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME.equals(path.segment(1))) + return; + Preferences root = Platform.getPreferencesService().getRootNode(); + String project = path.segment(0); + String qualifier = path.removeFileExtension().lastSegment(); + ProjectPreferences projectNode = (ProjectPreferences) root.node(ProjectScope.SCOPE).node(project); + // if the node isn't known then just return + try { + if (!projectNode.nodeExists(qualifier)) + return; + } catch (BackingStoreException e) { + // ignore + } + + // clear the preferences + clearNode(projectNode.node(qualifier)); + + // notifies the CharsetManager if needed + if (qualifier.equals(PREFS_REGULAR_QUALIFIER) || qualifier.equals(PREFS_DERIVED_QUALIFIER)) + preferencesChanged(file.getProject()); + } + + static void deleted(IFolder folder) throws CoreException { + IPath path = folder.getFullPath(); + int count = path.segmentCount(); + if (count != 2) + return; + // check if we are the .settings directory + if (!EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME.equals(path.segment(1))) + return; + Preferences root = Platform.getPreferencesService().getRootNode(); + // The settings dir has been removed/moved so remove all project prefs + // for the resource. + String project = path.segment(0); + Preferences projectNode = root.node(ProjectScope.SCOPE).node(project); + // check if we need to notify the charset manager + boolean hasResourcesSettings = getFile(folder, PREFS_REGULAR_QUALIFIER).exists() || getFile(folder, PREFS_DERIVED_QUALIFIER).exists(); + // remove the preferences + removeNode(projectNode); + // notifies the CharsetManager + if (hasResourcesSettings) + preferencesChanged(folder.getProject()); + } + + /* + * The whole project has been removed so delete all of the project settings + */ + static void deleted(IProject project) throws CoreException { + // The settings dir has been removed/moved so remove all project prefs + // for the resource. We have to do this now because (since we aren't + // synchronizing) there is short-circuit code that doesn't visit the + // children. + Preferences root = Platform.getPreferencesService().getRootNode(); + Preferences projectNode = root.node(ProjectScope.SCOPE).node(project.getName()); + // check if we need to notify the charset manager + boolean hasResourcesSettings = getFile(project, PREFS_REGULAR_QUALIFIER).exists() || getFile(project, PREFS_DERIVED_QUALIFIER).exists(); + // remove the preferences + removeNode(projectNode); + // notifies the CharsetManager + if (hasResourcesSettings) + preferencesChanged(project); + } + + static void deleted(IResource resource) throws CoreException { + switch (resource.getType()) { + case IResource.FILE : + deleted((IFile) resource); + return; + case IResource.FOLDER : + deleted((IFolder) resource); + return; + case IResource.PROJECT : + deleted((IProject) resource); + return; + } + } + + /* + * Return the preferences file for the given folder and qualifier. + */ + static IFile getFile(IFolder folder, String qualifier) { + Assert.isLegal(folder.getName().equals(DEFAULT_PREFERENCES_DIRNAME)); + return folder.getFile(new Path(qualifier).addFileExtension(PREFS_FILE_EXTENSION)); + } + + /* + * Return the preferences file for the given project and qualifier. + */ + static IFile getFile(IProject project, String qualifier) { + return project.getFile(new Path(DEFAULT_PREFERENCES_DIRNAME).append(qualifier).addFileExtension(PREFS_FILE_EXTENSION)); + } + + private static Properties loadProperties(IFile file) throws BackingStoreException { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Loading preferences from file: " + file.getFullPath()); //$NON-NLS-1$ + Properties result = new Properties(); + InputStream input = null; + try { + input = new BufferedInputStream(file.getContents(true)); + result.load(input); + } catch (CoreException e) { + String message = NLS.bind(Messages.preferences_loadException, file.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } catch (IOException e) { + String message = NLS.bind(Messages.preferences_loadException, file.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } finally { + FileUtil.safeClose(input); + } + return result; + } + + private static void preferencesChanged(IProject project) { + Workspace workspace = ((Workspace) ResourcesPlugin.getWorkspace()); + workspace.getCharsetManager().projectPreferencesChanged(project); + workspace.getContentDescriptionManager().projectPreferencesChanged(project); + } + + private static void read(ProjectPreferences node, IFile file) throws BackingStoreException, CoreException { + if (file == null || !file.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Unable to determine preference file or file does not exist for node: " + node.absolutePath()); //$NON-NLS-1$ + return; + } + Properties fromDisk = loadProperties(file); + // no work to do + if (fromDisk.isEmpty()) + return; + // create a new node to store the preferences in. + IExportedPreferences myNode = (IExportedPreferences) ExportedPreferences.newRoot().node(node.absolutePath()); + convertFromProperties((EclipsePreferences) myNode, fromDisk, false); + //flag that we are currently reading, to avoid unnecessary writing + boolean oldIsReading = node.isReading; + node.isReading = true; + try { + Platform.getPreferencesService().applyPreferences(myNode); + } finally { + node.isReading = oldIsReading; + } + } + + static void removeNode(Preferences node) throws CoreException { + String message = NLS.bind(Messages.preferences_removeNodeException, node.absolutePath()); + try { + node.removeNode(); + } catch (BackingStoreException e) { + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e); + throw new CoreException(status); + } + removeLoadedNodes(node); + } + + static void clearNode(Preferences node) throws CoreException { + // if the underlying properties file was deleted, clear the values and remove + // it from the list of loaded nodes, keep the node as it might still be referenced + try { + clearAll(node); + } catch (BackingStoreException e) { + String message = NLS.bind(Messages.preferences_clearNodeException, node.absolutePath()); + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e); + throw new CoreException(status); + } + removeLoadedNodes(node); + } + + private static void clearAll(Preferences node) throws BackingStoreException { + node.clear(); + String[] names = node.childrenNames(); + for (int i = 0; i < names.length; i++) { + clearAll(node.node(names[i])); + } + } + + private static void removeLoadedNodes(Preferences node) { + String path = node.absolutePath(); + synchronized (loadedNodes) { + for (Iterator i = loadedNodes.iterator(); i.hasNext();) { + String key = i.next(); + if (key.startsWith(path)) + i.remove(); + } + } + } + + public static void updatePreferences(IFile file) throws CoreException { + IPath path = file.getFullPath(); + // if we made it this far we are inside /project/.settings and might + // have a change to a preference file + if (!PREFS_FILE_EXTENSION.equals(path.getFileExtension())) + return; + + String project = path.segment(0); + String qualifier = path.removeFileExtension().lastSegment(); + Preferences root = Platform.getPreferencesService().getRootNode(); + Preferences node = root.node(ProjectScope.SCOPE).node(project).node(qualifier); + String message = null; + try { + message = NLS.bind(Messages.preferences_syncException, node.absolutePath()); + if (!(node instanceof ProjectPreferences)) + return; + ProjectPreferences projectPrefs = (ProjectPreferences) node; + if (projectPrefs.isWriting) + return; + read(projectPrefs, file); + // Bug 108066: In case the node had existed before it was updated from + // file, the read() operation marks it dirty. Override the dirty flag + // since we know that the node is expected to be in sync with the file. + projectPrefs.dirty = false; + + // make sure that we generate the appropriate resource change events + // if encoding settings have changed + if (PREFS_REGULAR_QUALIFIER.equals(qualifier) || PREFS_DERIVED_QUALIFIER.equals(qualifier)) + preferencesChanged(file.getProject()); + } catch (BackingStoreException e) { + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e); + throw new CoreException(status); + } + } + + /** + * Default constructor. Should only be called by #createExecutableExtension. + */ + public ProjectPreferences() { + super(null, null); + } + + private ProjectPreferences(EclipsePreferences parent, String name) { + super(parent, name); + + // cache the segment count + String path = absolutePath(); + segmentCount = getSegmentCount(path); + + if (segmentCount == 1) + return; + + // cache the project name + String projectName = getSegment(path, 1); + if (projectName != null) + project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); + + // cache the qualifier + if (segmentCount > 2) + qualifier = getSegment(path, 2); + } + + @Override + public String[] childrenNames() throws BackingStoreException { + // illegal state if this node has been removed + checkRemoved(); + initialize(); + silentLoad(); + return super.childrenNames(); + } + + @Override + public void clear() { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + super.clear(); + } + + /* + * Figure out what the children of this node are based on the resources + * that are in the workspace. + */ + private String[] computeChildren() { + if (project == null) + return EMPTY_STRING_ARRAY; + IFolder folder = project.getFolder(DEFAULT_PREFERENCES_DIRNAME); + if (!folder.exists()) + return EMPTY_STRING_ARRAY; + IResource[] members = null; + try { + members = folder.members(); + } catch (CoreException e) { + return EMPTY_STRING_ARRAY; + } + ArrayList result = new ArrayList<>(); + for (int i = 0; i < members.length; i++) { + IResource resource = members[i]; + if (resource.getType() == IResource.FILE && PREFS_FILE_EXTENSION.equals(resource.getFullPath().getFileExtension())) + result.add(resource.getFullPath().removeFileExtension().lastSegment()); + } + return result.toArray(EMPTY_STRING_ARRAY); + } + + @Override + public void flush() throws BackingStoreException { + if (isReading) + return; + isWriting = true; + try { + // call the internal method because we don't want to be synchronized, we will do that ourselves later. + IEclipsePreferences toFlush = super.internalFlush(); + //if we aren't at the right level, then flush the appropriate node + if (toFlush != null) + toFlush.flush(); + } finally { + isWriting = false; + } + } + + private IFile getFile() { + if (file == null) { + if (project == null || qualifier == null) + return null; + file = getFile(project, qualifier); + } + return file; + } + + /* + * Return the node at which these preferences are loaded/saved. + */ + @Override + protected IEclipsePreferences getLoadLevel() { + if (loadLevel == null) { + if (project == null || qualifier == null) + return null; + // Make it relative to this node rather than navigating to it from the root. + // Walk backwards up the tree starting at this node. + // This is important to avoid a chicken/egg thing on startup. + EclipsePreferences node = this; + for (int i = 3; i < segmentCount; i++) + node = (EclipsePreferences) node.parent(); + loadLevel = node; + } + return loadLevel; + } + + /* + * Calculate and return the file system location for this preference node. + * Use the absolute path of the node to find out the project name so + * we can get its location on disk. + * + * NOTE: we cannot cache the location since it may change over the course + * of the project life-cycle. + */ + @Override + protected IPath getLocation() { + if (project == null || qualifier == null) + return null; + IPath path = project.getLocation(); + return computeLocation(path, qualifier); + } + + @Override + protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) { + return new ProjectPreferences(nodeParent, nodeName); + } + + @Override + protected String internalGet(String key) { + // throw NPE if key is null + if (key == null) + throw new NullPointerException(); + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + return super.internalGet(key); + } + + @Override + protected String internalPut(String key, String newValue) { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + if ((segmentCount == 3) && PREFS_REGULAR_QUALIFIER.equals(qualifier) && (project != null)) { + if (ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS.equals(key)) { + CharsetManager charsetManager = ((Workspace) ResourcesPlugin.getWorkspace()).getCharsetManager(); + if (Boolean.parseBoolean(newValue)) + charsetManager.splitEncodingPreferences(project); + else + charsetManager.mergeEncodingPreferences(project); + } + } + return super.internalPut(key, newValue); + } + + private void initialize() { + if (segmentCount != 2) + return; + + // if already initialized, then skip this initialization + if (initialized) + return; + + // initialize the children only if project is opened + if (project.isOpen()) { + try { + synchronized (this) { + List addedNames = Arrays.asList(internalChildNames()); + String[] names = computeChildren(); + // add names only for nodes that were not added previously + for (int i = 0; i < names.length; i++) + if (!addedNames.contains(names[i])) + addChild(names[i], null); + } + } finally { + // mark as initialized so that subsequent project opening will not initialize preferences again + initialized = true; + } + } + } + + @Override + protected boolean isAlreadyLoaded(IEclipsePreferences node) { + return loadedNodes.contains(node.absolutePath()); + } + + @Override + public String[] keys() { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + return super.keys(); + } + + @Override + protected void load() throws BackingStoreException { + load(true); + } + + private void load(boolean reportProblems) throws BackingStoreException { + IFile localFile = getFile(); + if (localFile == null || !localFile.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Unable to determine preference file or file does not exist for node: " + absolutePath()); //$NON-NLS-1$ + return; + } + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Loading preferences from file: " + localFile.getFullPath()); //$NON-NLS-1$ + Properties fromDisk = new Properties(); + InputStream input = null; + try { + input = new BufferedInputStream(localFile.getContents(true)); + fromDisk.load(input); + convertFromProperties(this, fromDisk, true); + loadedNodes.add(absolutePath()); + } catch (CoreException e) { + if (reportProblems) { + String message = NLS.bind(Messages.preferences_loadException, localFile.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } + } catch (IOException e) { + if (reportProblems) { + String message = NLS.bind(Messages.preferences_loadException, localFile.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } + } finally { + FileUtil.safeClose(input); + } + } + + /** + * If we are at the /project node and we are checking for the existence of a child, we + * want special behaviour. If the child is a single segment name, then we want to + * return true if the node exists OR if a project with that name exists in the workspace. + */ + @Override + public boolean nodeExists(String path) throws BackingStoreException { + // short circuit for checking this node + if (path.length() == 0) + return !removed; + // illegal state if this node has been removed. + // do this AFTER checking for the empty string. + checkRemoved(); + initialize(); + silentLoad(); + if (segmentCount != 1) + return super.nodeExists(path); + if (path.length() == 0) + return super.nodeExists(path); + if (path.charAt(0) == IPath.SEPARATOR) + return super.nodeExists(path); + if (path.indexOf(IPath.SEPARATOR) != -1) + return super.nodeExists(path); + // if we are checking existance of a single segment child of /project, base the answer on + // whether or not it exists in the workspace. + return ResourcesPlugin.getWorkspace().getRoot().getProject(path).exists() || super.nodeExists(path); + } + + @Override + public void remove(String key) { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + super.remove(key); + if ((segmentCount == 3) && PREFS_REGULAR_QUALIFIER.equals(qualifier) && (project != null)) { + if (ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS.equals(key)) { + CharsetManager charsetManager = ((Workspace) ResourcesPlugin.getWorkspace()).getCharsetManager(); + if (ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS) + charsetManager.splitEncodingPreferences(project); + else + charsetManager.mergeEncodingPreferences(project); + } + } + } + + @Override + protected void save() throws BackingStoreException { + final IFile fileInWorkspace = getFile(); + if (fileInWorkspace == null) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Not saving preferences since there is no file for node: " + absolutePath()); //$NON-NLS-1$ + return; + } + final String finalQualifier = qualifier; + final BackingStoreException[] bse = new BackingStoreException[1]; + try { + ICoreRunnable operation = new ICoreRunnable() { + @Override + public void run(IProgressMonitor monitor) throws CoreException { + try { + Properties table = convertToProperties(new SortedProperties(), ""); //$NON-NLS-1$ + // nothing to save. delete existing file if one exists. + if (table.isEmpty()) { + if (fileInWorkspace.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Deleting preference file: " + fileInWorkspace.getFullPath()); //$NON-NLS-1$ + if (fileInWorkspace.isReadOnly()) { + IStatus status = fileInWorkspace.getWorkspace().validateEdit(new IFile[] {fileInWorkspace}, IWorkspace.VALIDATE_PROMPT); + if (!status.isOK()) + throw new CoreException(status); + } + try { + fileInWorkspace.delete(true, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.preferences_deleteException, fileInWorkspace.getFullPath()); + log(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IStatus.WARNING, message, null)); + } + } + return; + } + table.put(VERSION_KEY, VERSION_VALUE); + // print the table to a string and remove the timestamp that Properties#store always adds + String s = removeTimestampFromTable(table); + String systemLineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$ + String fileLineSeparator = FileUtil.getLineSeparator(fileInWorkspace); + if (!systemLineSeparator.equals(fileLineSeparator)) + s = s.replaceAll(systemLineSeparator, fileLineSeparator); + InputStream input = new BufferedInputStream(new ByteArrayInputStream(s.getBytes("UTF-8"))); //$NON-NLS-1$ + if (fileInWorkspace.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Setting preference file contents for: " + fileInWorkspace.getFullPath()); //$NON-NLS-1$ + if (fileInWorkspace.isReadOnly()) { + IStatus status = fileInWorkspace.getWorkspace().validateEdit(new IFile[] {fileInWorkspace}, IWorkspace.VALIDATE_PROMPT); + if (!status.isOK()) { + input.close(); + throw new CoreException(status); + } + } + // set the contents + fileInWorkspace.setContents(input, IResource.KEEP_HISTORY, null); + } else { + // create the file + IFolder folder = (IFolder) fileInWorkspace.getParent(); + if (!folder.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Creating parent preference directory: " + folder.getFullPath()); //$NON-NLS-1$ + folder.create(IResource.NONE, true, null); + } + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Creating preference file: " + fileInWorkspace.getLocation()); //$NON-NLS-1$ + fileInWorkspace.create(input, IResource.NONE, null); + } + if (PREFS_DERIVED_QUALIFIER.equals(finalQualifier)) + fileInWorkspace.setDerived(true, null); + } catch (BackingStoreException e) { + bse[0] = e; + } catch (IOException e) { + String message = NLS.bind(Messages.preferences_saveProblems, fileInWorkspace.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + bse[0] = new BackingStoreException(message); + } + } + }; + //don't bother with scheduling rules if we are already inside an operation + try { + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + if (((Workspace) workspace).getWorkManager().isLockAlreadyAcquired()) { + operation.run(null); + } else { + IResourceRuleFactory factory = workspace.getRuleFactory(); + // we might: delete the file, create the .settings folder, create the file, modify the file, or set derived flag for the file. + ISchedulingRule rule = MultiRule.combine(new ISchedulingRule[] {factory.deleteRule(fileInWorkspace), factory.createRule(fileInWorkspace.getParent()), factory.modifyRule(fileInWorkspace), factory.derivedRule(fileInWorkspace)}); + workspace.run(operation, rule, IResource.NONE, null); + if (bse[0] != null) + throw bse[0]; + } + } catch (OperationCanceledException e) { + throw new BackingStoreException(Messages.preferences_operationCanceled); + } + } catch (CoreException e) { + String message = NLS.bind(Messages.preferences_saveProblems, fileInWorkspace.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } + } + + private void silentLoad() { + ProjectPreferences node = (ProjectPreferences) getLoadLevel(); + if (node == null) + return; + if (isAlreadyLoaded(node) || node.isLoading()) + return; + try { + node.setLoading(true); + node.load(false); + } catch (BackingStoreException e) { + // will not happen, all exceptions are swallowed by load(false) + } finally { + node.setLoading(false); + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java new file mode 100644 index 0000000000..c4951f6ce6 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.HashMap; +import java.util.Map; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * Repository for all variable providers available through the extension points. + * @since 3.6 + */ +public class ProjectVariableProviderManager { + + public static class Descriptor { + PathVariableResolver provider = null; + String name = null; + String value = null; + + public Descriptor(IExtension extension, IConfigurationElement element) throws RuntimeException, CoreException { + name = element.getAttribute("variable"); //$NON-NLS-1$ + value = element.getAttribute("value"); //$NON-NLS-1$ + try { + String classAttribute = "class"; //$NON-NLS-1$ + if (element.getAttribute(classAttribute) != null) + provider = (PathVariableResolver) element.createExecutableExtension(classAttribute); + } catch (CoreException e) { + Policy.log(e); + } + if (name == null) + fail(NLS.bind(Messages.mapping_invalidDef, extension.getUniqueIdentifier())); + } + + protected void fail(String reason) throws CoreException { + throw new ResourceException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, reason, null)); + } + + public String getName() { + return name; + } + + public String getValue(String variable, IResource resource) { + if (value != null) + return value; + return provider.getValue(variable, resource); + } + + public String[] getVariableNames(String variable, IResource resource) { + if (provider != null) + return provider.getVariableNames(variable, resource); + if (name.equals(variable)) + return new String[] {variable}; + return null; + } + } + + private static Map descriptors; + private static Descriptor[] descriptorsArray; + private static ProjectVariableProviderManager instance = new ProjectVariableProviderManager(); + + public static ProjectVariableProviderManager getDefault() { + return instance; + } + + public Descriptor[] getDescriptors() { + lazyInitialize(); + return descriptorsArray; + } + + protected void lazyInitialize() { + if (descriptors != null) + return; + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_VARIABLE_PROVIDERS); + IExtension[] extensions = point.getExtensions(); + descriptors = new HashMap<>(extensions.length * 2 + 1); + for (int i = 0, imax = extensions.length; i < imax; i++) { + IConfigurationElement[] elements = extensions[i].getConfigurationElements(); + int count = elements.length; + for (int j = 0; j < count; j++) { + IConfigurationElement element = elements[j]; + String elementName = element.getName(); + if (elementName.equalsIgnoreCase("variableResolver")) { //$NON-NLS-1$ + Descriptor desc = null; + try { + desc = new Descriptor(extensions[i], element); + } catch (CoreException e) { + Policy.log(e); + } + if (desc != null) + descriptors.put(desc.getName(), desc); + } + } + } + descriptorsArray = descriptors.values().toArray(new Descriptor[descriptors.size()]); + } + + public Descriptor findDescriptor(String name) { + lazyInitialize(); + Descriptor result = descriptors.get(name); + return result; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java new file mode 100644 index 0000000000..bd1b8d2639 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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 development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.regex.*; +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.filtermatchers.AbstractFileInfoMatcher; +import org.eclipse.core.runtime.*; + +/** + * A Filter provider for Java Regular expression supported by + * java.util.regex.Pattern. + */ +public class RegexFileInfoMatcher extends AbstractFileInfoMatcher { + + Pattern pattern = null; + + public RegexFileInfoMatcher() { + // nothing to do + } + + @Override + public boolean matches(IContainer parent, IFileInfo fileInfo) { + if (pattern != null) { + Matcher m = pattern.matcher(fileInfo.getName()); + return m.matches(); + } + return false; + } + + @Override + public void initialize(IProject project, Object arguments) throws CoreException { + if (arguments != null) { + try { + pattern = Pattern.compile((String) arguments); + } catch (PatternSyntaxException e) { + throw new CoreException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, Platform.PLUGIN_ERROR, e.getMessage(), e)); + } + } + } +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java new file mode 100644 index 0000000000..ff3fad04ba --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java @@ -0,0 +1,2084 @@ +/******************************************************************************* + * 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 + * Dan Rubel - Implementation of getLocalTimeStamp + * Red Hat Incorporated - get/setResourceAttribute code + * Oakland Software Incorporated - added getSessionProperties and getPersistentProperties + * Holger Oehm - [226264] race condition in Workspace.isTreeLocked()/setTreeLocked() + * Martin Oberhuber (Wind River) - [245937] ProjectDescription#setLinkLocation() detects non-change + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support + * James Blackburn (Broadcom Corp.) - ongoing development + * Sergey Prigogin (Google) - [338010] Resource.createLink() does not preserve symbolic links + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.internal.events.LifecycleEvent; +import org.eclipse.core.internal.localstore.FileSystemResourceManager; +import org.eclipse.core.internal.properties.IPropertyManager; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.internal.watson.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IMoveDeleteHook; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; +import org.eclipse.osgi.util.NLS; + +public abstract class Resource extends PlatformObject implements IResource, ICoreConstants, Cloneable, IPathRequestor { + /* package */IPath path; + /* package */Workspace workspace; + + protected Resource(IPath path, Workspace workspace) { + this.path = path.removeTrailingSeparator(); + this.workspace = workspace; + } + + @Override + public void accept(IResourceProxyVisitor visitor, int memberFlags) throws CoreException { + accept(visitor, IResource.DEPTH_INFINITE, memberFlags); + } + + @Override + public void accept(final IResourceProxyVisitor visitor, final int depth, final int memberFlags) throws CoreException { + // it is invalid to call accept on a phantom when INCLUDE_PHANTOMS is not specified + final boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0; + if ((memberFlags & IContainer.DO_NOT_CHECK_EXISTENCE) == 0) + checkAccessible(getFlags(getResourceInfo(includePhantoms, false))); + + final ResourceProxy proxy = new ResourceProxy(); + IElementContentVisitor elementVisitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object contents) { + ResourceInfo info = (ResourceInfo) contents; + if (!isMember(getFlags(info), memberFlags)) + return false; + proxy.requestor = requestor; + proxy.info = info; + try { + boolean shouldContinue = true; + switch (depth) { + case DEPTH_ZERO : + shouldContinue = false; + break; + case DEPTH_ONE : + shouldContinue = !path.equals(requestor.requestPath().removeLastSegments(1)); + break; + case DEPTH_INFINITE : + shouldContinue = true; + break; + } + return visitor.visit(proxy) && shouldContinue; + } catch (CoreException e) { + //throw an exception to bail out of the traversal + throw new WrappedRuntimeException(e); + } finally { + proxy.reset(); + } + } + }; + try { + new ElementTreeIterator(workspace.getElementTree(), getFullPath()).iterate(elementVisitor); + } catch (WrappedRuntimeException e) { + throw (CoreException) e.getTargetException(); + } finally { + proxy.requestor = null; + proxy.info = null; + } + } + + @Override + public void accept(IResourceVisitor visitor) throws CoreException { + accept(visitor, IResource.DEPTH_INFINITE, 0); + } + + @Override + public void accept(IResourceVisitor visitor, int depth, boolean includePhantoms) throws CoreException { + accept(visitor, depth, includePhantoms ? IContainer.INCLUDE_PHANTOMS : 0); + } + + @Override + public void accept(final IResourceVisitor visitor, int depth, int memberFlags) throws CoreException { + //use the fast visitor if visiting to infinite depth + if (depth == IResource.DEPTH_INFINITE) { + accept(new IResourceProxyVisitor() { + @Override + public boolean visit(IResourceProxy proxy) throws CoreException { + return visitor.visit(proxy.requestResource()); + } + }, memberFlags); + return; + } + // it is invalid to call accept on a phantom when INCLUDE_PHANTOMS is not specified + final boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0; + ResourceInfo info = getResourceInfo(includePhantoms, false); + int flags = getFlags(info); + if ((memberFlags & IContainer.DO_NOT_CHECK_EXISTENCE) == 0) + checkAccessible(flags); + + //check that this resource matches the member flags + if (!isMember(flags, memberFlags)) + return; + // visit this resource + if (!visitor.visit(this) || depth == DEPTH_ZERO) + return; + // get the info again because it might have been changed by the visitor + info = getResourceInfo(includePhantoms, false); + if (info == null) + return; + // thread safety: (cache the type to avoid changes -- we might not be inside an operation) + int type = info.getType(); + if (type == FILE) + return; + // if we had a gender change we need to fix up the resource before asking for its members + IContainer resource = getType() != type ? (IContainer) workspace.newResource(getFullPath(), type) : (IContainer) this; + IResource[] members = resource.members(memberFlags); + for (int i = 0; i < members.length; i++) + members[i].accept(visitor, DEPTH_ZERO, memberFlags | IContainer.DO_NOT_CHECK_EXISTENCE); + } + + protected void assertCopyRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + IStatus status = checkCopyRequirements(destination, destinationType, updateFlags); + if (!status.isOK()) { + // this assert is ok because the error cases generated by the + // check method above indicate assertion conditions. + Assert.isTrue(false, status.getChildren()[0].getMessage()); + } + } + + /** + * Throws an exception if the link preconditions are not met. Returns the file info + * for the file being linked to, or null if not available. + * @throws CoreException + */ + protected IFileInfo assertLinkRequirements(URI localLocation, int updateFlags) throws CoreException { + boolean allowMissingLocal = (updateFlags & IResource.ALLOW_MISSING_LOCAL) != 0; + if ((updateFlags & IResource.REPLACE) == 0) + checkDoesNotExist(getFlags(getResourceInfo(false, false)), true); + IStatus locationStatus = workspace.validateLinkLocationURI(this, localLocation); + //we only tolerate an undefined path variable in the allow missing local case + final boolean variableUndefined = locationStatus.getCode() == IResourceStatus.VARIABLE_NOT_DEFINED_WARNING; + if (locationStatus.getSeverity() == IStatus.ERROR || (variableUndefined && !allowMissingLocal)) + throw new ResourceException(locationStatus); + //check that the parent exists and is open + Container parent = (Container) getParent(); + parent.checkAccessible(getFlags(parent.getResourceInfo(false, false))); + //if the variable is undefined we can't do any further checks + if (variableUndefined) + return null; + //check if the file exists + URI resolved = getPathVariableManager().resolveURI(localLocation); + IFileStore store = EFS.getStore(resolved); + IFileInfo fileInfo = store.fetchInfo(); + boolean localExists = fileInfo.exists(); + if (!allowMissingLocal && !localExists) { + String msg = NLS.bind(Messages.links_localDoesNotExist, store.toString()); + throw new ResourceException(IResourceStatus.NOT_FOUND_LOCAL, getFullPath(), msg, null); + } + //resource type and file system type must match + if (localExists && ((getType() == IResource.FOLDER) != fileInfo.isDirectory())) { + String msg = NLS.bind(Messages.links_wrongLocalType, getFullPath()); + throw new ResourceException(IResourceStatus.WRONG_TYPE_LOCAL, getFullPath(), msg, null); + } + return fileInfo; + } + + protected void assertMoveRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + IStatus status = checkMoveRequirements(destination, destinationType, updateFlags); + if (!status.isOK()) { + // this assert is ok because the error cases generated by the + // check method above indicate assertion conditions. + Assert.isTrue(false, status.getChildren()[0].getMessage()); + } + } + + public void checkAccessible(int flags) throws CoreException { + checkExists(flags, true); + } + + private ResourceInfo checkAccessibleAndLocal(int depth) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + checkLocal(flags, depth); + return info; + } + + /** + * This method reports errors in two different ways. It can throw a + * CoreException or return a status. CoreExceptions are used according to the + * specification of the copy method. Programming errors, that would usually be + * prevented by using an "Assert" code, are reported as an IStatus. We're doing + * this way because we have two different methods to copy resources: + * IResource#copy and IWorkspace#copy. The first one gets the error and throws + * its message in an AssertionFailureException. The second one just throws a + * CoreException using the status returned by this method. + * + * @see IResource#copy(IPath, int, IProgressMonitor) + */ + public IStatus checkCopyRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + String message = Messages.resources_copyNotMet; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_VALUE, message, null); + if (destination == null) { + message = Messages.resources_destNotNull; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message); + } + destination = makePathAbsolute(destination); + if (getFullPath().isPrefixOf(destination)) { + message = NLS.bind(Messages.resources_copyDestNotSub, getFullPath()); + status.add(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message)); + } + checkValidPath(destination, destinationType, false); + + ResourceInfo info; + checkAccessibleAndLocal(DEPTH_INFINITE); + + IPath destinationParent = destination.removeLastSegments(1); + checkValidGroupContainer(destinationParent, isLinked(), isVirtual()); + + Resource dest = workspace.newResource(destination, destinationType); + dest.checkDoesNotExist(); + + // ensure we aren't trying to copy a file to a project + if (getType() == IResource.FILE && destinationType == IResource.PROJECT) { + message = Messages.resources_fileToProj; + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + + // we can't copy into a closed project + if (destinationType != IResource.PROJECT) { + Project project = (Project) dest.getProject(); + info = project.getResourceInfo(false, false); + project.checkAccessible(getFlags(info)); + Container parent = (Container) dest.getParent(); + if (!parent.equals(project)) { + info = parent.getResourceInfo(false, false); + parent.checkExists(getFlags(info), true); + } + } + if (isUnderLink() || dest.isUnderLink()) { + //make sure location is not null. This can occur with linked resources relative to + //undefined path variables + URI sourceLocation = getLocationURI(); + if (sourceLocation == null) { + message = NLS.bind(Messages.localstore_locationUndefined, getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, getFullPath(), message, null); + } + URI destLocation = dest.getLocationURI(); + if (destLocation == null && (dest.isUnderVirtual() == false)) { + message = NLS.bind(Messages.localstore_locationUndefined, dest.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, dest.getFullPath(), message, null); + } + //make sure location of source is not a prefix of the location of the destination + //this can occur if the source and/or destination is a linked resource + if (getStore().isParentOf(dest.getStore())) { + message = NLS.bind(Messages.resources_copyDestNotSub, getFullPath()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + } + + return status.isOK() ? Status.OK_STATUS : (IStatus) status; + } + + /** + * Checks that this resource does not exist. If the file system is not case + * sensitive, this method also checks for a case variant. + */ + protected void checkDoesNotExist() throws CoreException { + checkDoesNotExist(getFlags(getResourceInfo(false, false)), false); + } + + /** + * Checks that this resource does not exist. If the file system is not case + * sensitive, this method also checks for a case variant. + * + * @exception CoreException if this resource exists + */ + public void checkDoesNotExist(int flags, boolean checkType) throws CoreException { + //if this exact resource exists we are done + if (exists(flags, checkType)) { + String message = NLS.bind(Messages.resources_mustNotExist, getFullPath()); + throw new ResourceException(checkType ? IResourceStatus.RESOURCE_EXISTS : IResourceStatus.PATH_OCCUPIED, getFullPath(), message, null); + } + if (Workspace.caseSensitive) + return; + //now look for a matching case variant in the tree + IResource variant = findExistingResourceVariant(getFullPath()); + if (variant == null) + return; + String msg = NLS.bind(Messages.resources_existsDifferentCase, variant.getFullPath()); + throw new ResourceException(IResourceStatus.CASE_VARIANT_EXISTS, variant.getFullPath(), msg, null); + } + + /** + * Checks that this resource exists. + * If checkType is true, the type of this resource and the one in the tree must match. + * + * @exception CoreException if this resource does not exist + */ + public void checkExists(int flags, boolean checkType) throws CoreException { + if (!exists(flags, checkType)) { + String message = NLS.bind(Messages.resources_mustExist, getFullPath()); + throw new ResourceException(IResourceStatus.RESOURCE_NOT_FOUND, getFullPath(), message, null); + } + } + + /** + * Checks that this resource is local to the given depth. + * + * @exception CoreException if this resource is not local + */ + public void checkLocal(int flags, int depth) throws CoreException { + if (!isLocal(flags, depth)) { + String message = NLS.bind(Messages.resources_mustBeLocal, getFullPath()); + throw new ResourceException(IResourceStatus.RESOURCE_NOT_LOCAL, getFullPath(), message, null); + } + } + + /** + * This method reports errors in two different ways. It can throw a + * CoreException or log a status. CoreExceptions are used according + * to the specification of the move method. Programming errors, that + * would usually be prevented by using an "Assert" code, are reported as + * an IStatus. + * We're doing this way because we have two different methods to move + * resources: IResource#move and IWorkspace#move. The first one gets + * the error and throws its message in an AssertionFailureException. The + * second one just throws a CoreException using the status returned + * by this method. + * + * @see IResource#move(IPath, int, IProgressMonitor) + */ + protected IStatus checkMoveRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + String message = Messages.resources_moveNotMet; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_VALUE, message, null); + if (destination == null) { + message = Messages.resources_destNotNull; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message); + } + destination = makePathAbsolute(destination); + if (getFullPath().isPrefixOf(destination)) { + message = NLS.bind(Messages.resources_moveDestNotSub, getFullPath()); + status.add(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message)); + } + checkValidPath(destination, destinationType, false); + + ResourceInfo info; + checkAccessibleAndLocal(DEPTH_INFINITE); + + IPath destinationParent = destination.removeLastSegments(1); + checkValidGroupContainer(destinationParent, isLinked(), isVirtual()); + + Resource dest = workspace.newResource(destination, destinationType); + + // check if we are only changing case + IResource variant = Workspace.caseSensitive ? null : findExistingResourceVariant(destination); + if (variant == null || !this.equals(variant)) + dest.checkDoesNotExist(); + + // ensure we aren't trying to move a file to a project + if (getType() == IResource.FILE && dest.getType() == IResource.PROJECT) { + message = Messages.resources_fileToProj; + throw new ResourceException(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message)); + } + + // we can't move into a closed project + if (destinationType != IResource.PROJECT) { + Project project = (Project) dest.getProject(); + info = project.getResourceInfo(false, false); + project.checkAccessible(getFlags(info)); + Container parent = (Container) dest.getParent(); + if (!parent.equals(project)) { + info = parent.getResourceInfo(false, false); + parent.checkExists(getFlags(info), true); + } + } + if (isUnderLink() || dest.isUnderLink()) { + //make sure location is not null. This can occur with linked resources relative to + //undefined path variables + URI sourceLocation = getLocationURI(); + if (sourceLocation == null) { + message = NLS.bind(Messages.localstore_locationUndefined, getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, getFullPath(), message, null); + } + URI destLocation = dest.getLocationURI(); + if (destLocation == null && (dest.isUnderVirtual() == false)) { + message = NLS.bind(Messages.localstore_locationUndefined, dest.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, dest.getFullPath(), message, null); + } + //make sure location of source is not a prefix of the location of the destination + //this can occur if the source and/or destination is a linked resource + if (getStore().isParentOf(dest.getStore())) { + message = NLS.bind(Messages.resources_moveDestNotSub, getFullPath()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + } + + return status.isOK() ? Status.OK_STATUS : (IStatus) status; + } + + /** + * Checks that the supplied path is valid according to Workspace.validatePath(). + * + * @exception CoreException if the path is not valid + */ + public void checkValidPath(IPath toValidate, int type, boolean lastSegmentOnly) throws CoreException { + IStatus result = workspace.locationValidator.validatePath(toValidate, type, lastSegmentOnly); + if (!result.isOK()) + throw new ResourceException(result); + } + + /** + * Checks that the destination is a suitable one given that it could be a + * group. + * + * @exception CoreException + * if the path points to a group + */ + public void checkValidGroupContainer(IPath destination, boolean isLink, boolean isGroup) throws CoreException { + if (!isLink && !isGroup) { + String message = Messages.group_invalidParent; + ResourceInfo info = workspace.getResourceInfo(destination, false, false); + if (info != null && info.isSet(M_VIRTUAL)) + throw new ResourceException(new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message)); + } + } + + /** + * Checks that the destination is a suitable one given that it could be a + * group. + * + * @exception CoreException + * if the path points to a group + */ + public void checkValidGroupContainer(Container destination, boolean isLink, boolean isGroup) throws CoreException { + if (!isLink && !isGroup) { + String message = Messages.group_invalidParent; + if (destination.isVirtual()) + throw new ResourceException(new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message)); + } + } + + public IStatus getValidGroupContainer(IPath destination, boolean isLink, boolean isGroup) { + if (!isLink && !isGroup) { + String message = Messages.group_invalidParent; + ResourceInfo info = workspace.getResourceInfo(destination, false, false); + if (info.isSet(M_VIRTUAL)) + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + return Status.OK_STATUS; + } + + @Override + public void clearHistory(IProgressMonitor monitor) { + getLocalManager().getHistoryStore().remove(getFullPath(), monitor); + } + + @Override + public boolean contains(ISchedulingRule rule) { + if (this == rule) + return true; + //must allow notifications to nest in all resource rules + if (rule.getClass().equals(WorkManager.NotifyRule.class)) + return true; + if (rule instanceof MultiRule) { + MultiRule multi = (MultiRule) rule; + ISchedulingRule[] children = multi.getChildren(); + for (int i = 0; i < children.length; i++) + if (!contains(children[i])) + return false; + return true; + } + if (!(rule instanceof IResource)) + return false; + IResource resource = (IResource) rule; + if (!workspace.equals(resource.getWorkspace())) + return false; + return path.isPrefixOf(resource.getFullPath()); + } + + /** + * @throws CoreException + */ + public void convertToPhantom() throws CoreException { + ResourceInfo info = getResourceInfo(false, true); + if (info == null || isPhantom(getFlags(info))) + return; + info.clearSessionProperties(); + info.set(M_PHANTOM); + getLocalManager().updateLocalSync(info, I_NULL_SYNC_INFO); + info.clearModificationStamp(); + // should already be done by the #deleteResource call but left in + // just to be safe and for code clarity. + info.setMarkers(null); + } + + @Override + public void copy(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + copy(destination, updateFlags, monitor); + } + + @Override + public void copy(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + try { + monitor = Policy.monitorFor(monitor); + String message = NLS.bind(Messages.resources_copying, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + Policy.checkCanceled(monitor); + destination = makePathAbsolute(destination); + checkValidPath(destination, getType(), false); + Resource destResource = workspace.newResource(destination, getType()); + final ISchedulingRule rule = workspace.getRuleFactory().copyRule(this, destResource); + try { + workspace.prepareOperation(rule, monitor); + // The following assert method throws CoreExceptions as stated in the IResource.copy API + // and assert for programming errors. See checkCopyRequirements for more information. + assertCopyRequirements(destination, getType(), updateFlags); + workspace.beginOperation(true); + getLocalManager().copy(this, destResource, updateFlags, 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(); + } + } + + @Override + public void copy(IProjectDescription destDesc, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + copy(destDesc, updateFlags, monitor); + } + + /* (non-Javadoc) + * Used when a folder is to be copied to a project. + * @see IResource#copy(IProjectDescription, int, IProgressMonitor) + */ + @Override + public void copy(IProjectDescription destDesc, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(destDesc); + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_copying, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + try { + workspace.prepareOperation(workspace.getRoot(), monitor); + // The following assert method throws CoreExceptions as stated in the IResource.copy API + // and assert for programming errors. See checkCopyRequirements for more information. + IPath destPath = new Path(destDesc.getName()).makeAbsolute(); + assertCopyRequirements(destPath, getType(), updateFlags); + Project destProject = (Project) workspace.getRoot().getProject(destPath.lastSegment()); + workspace.beginOperation(true); + + // create and open the new project + destProject.create(destDesc, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + destProject.open(Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + + // copy the children + // FIXME: fix the progress monitor here...create a sub monitor and do a worked(1) after each child instead + IResource[] children = ((IContainer) this).members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < children.length; i++) { + Resource child = (Resource) children[i]; + child.copy(destPath.append(child.getName()), updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 60 / 100 / children.length)); + } + + // copy over the properties + getPropertyManager().copy(this, destProject, DEPTH_ZERO); + monitor.worked(Policy.opWork * 15 / 100); + + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(workspace.getRoot(), true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + /** + * Count the number of resources in the tree from this container to the + * specified depth. Include this resource. Include phantoms if + * the phantom boolean is true. + */ + public int countResources(int depth, boolean phantom) { + return workspace.countResources(path, depth, phantom); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IFolder#createLink(IPath, int, IProgressMonitor) + * @see org.eclipse.core.resources.IFile#createLink(IPath, int, IProgressMonitor) + */ + public void createLink(IPath localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(localLocation); + createLink(URIUtil.toURI(localLocation), updateFlags, monitor); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IFolder#createLink(URI, int, IProgressMonitor) + * @see org.eclipse.core.resources.IFile#createLink(URI, int, IProgressMonitor) + */ + public void createLink(URI localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(localLocation); + monitor = Policy.monitorFor(monitor); + IResource existing = null; + if ((updateFlags & REPLACE) != 0) { + existing = workspace.getRoot().findMember(getFullPath()); + if (existing != null && existing.isLinked()) { + setLinkLocation(localLocation, updateFlags, monitor); + return; + } + } + try { + String message = NLS.bind(Messages.links_creating, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + Policy.checkCanceled(monitor); + checkValidPath(path, FOLDER, true); + final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); + try { + workspace.prepareOperation(rule, monitor); + IFileInfo fileInfo = assertLinkRequirements(localLocation, updateFlags); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_CREATE, this)); + workspace.beginOperation(true); + //replace existing resource, if applicable + if ((updateFlags & REPLACE) != 0) { + if (existing != null) + workspace.deleteResource(existing); + } + ResourceInfo info = workspace.createResource(this, false); + if ((updateFlags & IResource.HIDDEN) != 0) + info.set(M_HIDDEN); + info.set(M_LINK); + LinkDescription linkDescription = new LinkDescription(this, localLocation); + if (linkDescription.isGroup()) + info.set(M_VIRTUAL); + getLocalManager().link(this, localLocation, fileInfo); + monitor.worked(Policy.opWork * 5 / 100); + //save the location in the project description + Project project = (Project) getProject(); + boolean changed = project.internalGetDescription().setLinkLocation(getProjectRelativePath(), linkDescription); + if (changed) + try { + project.writeDescription(IResource.NONE); + } catch (CoreException e) { + // a problem happened updating the description, so delete the resource from the workspace + workspace.deleteResource(this); + throw e; // rethrow + } + 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 IMarker createMarker(String type) throws CoreException { + Assert.isNotNull(type); + final ISchedulingRule rule = workspace.getRuleFactory().markerRule(this); + try { + workspace.prepareOperation(rule, null); + checkAccessible(getFlags(getResourceInfo(false, false))); + workspace.beginOperation(true); + MarkerInfo info = new MarkerInfo(); + info.setType(type); + info.setCreationTime(System.currentTimeMillis()); + workspace.getMarkerManager().add(this, info); + return new Marker(this, info.getId()); + } finally { + workspace.endOperation(rule, false, null); + } + } + + @Override + public IResourceProxy createProxy() { + ResourceProxy result = new ResourceProxy(); + result.info = getResourceInfo(false, false); + result.requestor = this; + result.resource = this; + return result; + } + + /* (non-Javadoc) + * @see IProject#delete(boolean, boolean, IProgressMonitor) + * @see IWorkspaceRoot#delete(boolean, boolean, IProgressMonitor) + * N.B. This is not an IResource method! + */ + public void delete(boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; + delete(updateFlags, monitor); + } + + @Override + public void delete(boolean force, IProgressMonitor monitor) throws CoreException { + delete(force ? IResource.FORCE : IResource.NONE, monitor); + } + + @Override + public void delete(int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_deleting, getFullPath()); + monitor.beginTask("", Policy.totalWork * 1000); //$NON-NLS-1$ + monitor.subTask(message); + final ISchedulingRule rule = workspace.getRuleFactory().deleteRule(this); + try { + workspace.prepareOperation(rule, monitor); + // if there is no resource then there is nothing to delete so just return + if (!exists()) + return; + workspace.beginOperation(true); + broadcastPreDeleteEvent(); + + // when a project is being deleted, flush the build order in case there is a problem + if (this.getType() == IResource.PROJECT) + workspace.flushBuildOrder(); + + final IFileStore originalStore = getStore(); + boolean wasLinked = isLinked(); + message = Messages.resources_deleteProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_DELETE_LOCAL, message, null); + WorkManager workManager = workspace.getWorkManager(); + ResourceTree tree = new ResourceTree(workspace.getFileSystemManager(), workManager.getLock(), status, updateFlags); + int depth = 0; + try { + depth = workManager.beginUnprotected(); + unprotectedDelete(tree, updateFlags, monitor); + } finally { + workManager.endUnprotected(depth); + } + if (getType() == ROOT) { + // need to clear out the root info + workspace.getMarkerManager().removeMarkers(this, IResource.DEPTH_ZERO); + getPropertyManager().deleteProperties(this, IResource.DEPTH_ZERO); + getResourceInfo(false, false).clearSessionProperties(); + } + // Invalidate the tree for further use by clients. + tree.makeInvalid(); + if (!tree.getStatus().isOK()) + throw new ResourceException(tree.getStatus()); + //update any aliases of this resource + //note that deletion of a linked resource cannot affect other resources + if (!wasLinked) + workspace.getAliasManager().updateAliases(this, originalStore, IResource.DEPTH_INFINITE, monitor); + if (getType() == PROJECT) { + // make sure the rule factory is cleared on project deletion + ((Rules) workspace.getRuleFactory()).setRuleFactory((IProject) this, null); + // make sure project deletion is remembered + workspace.getSaveManager().requestSnapshot(); + } + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork * 1000)); + } + } finally { + monitor.done(); + } + } + + @Override + public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException { + final ISchedulingRule rule = workspace.getRuleFactory().markerRule(this); + try { + workspace.prepareOperation(rule, null); + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + + workspace.beginOperation(true); + workspace.getMarkerManager().removeMarkers(this, type, includeSubtypes, depth); + } finally { + workspace.endOperation(rule, false, null); + } + } + + /** + * This method should be called to delete a resource from the tree because it will also + * delete its properties and markers. If a status object is provided, minor exceptions are + * added, otherwise they are thrown. If major exceptions occur, they are always thrown. + */ + public void deleteResource(boolean convertToPhantom, MultiStatus status) throws CoreException { + // remove markers on this resource and its descendents + if (exists()) + getMarkerManager().removeMarkers(this, IResource.DEPTH_INFINITE); + // if this is a linked resource or contains linked resources , remove their entries from the project description + List links = findLinks(); + //pre-delete notification to internal infrastructure + if (links != null) + for (Iterator it = links.iterator(); it.hasNext();) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_DELETE, it.next())); + + // check if we deleted a preferences file + ProjectPreferences.deleted(this); + + //remove all deleted linked resources from the project description + if (getType() != IResource.PROJECT && links != null) { + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + if (description != null) { + boolean wasChanged = false; + for (Iterator it = links.iterator(); it.hasNext();) + wasChanged |= description.setLinkLocation(it.next().getProjectRelativePath(), null); + if (wasChanged) { + project.internalSetDescription(description, true); + try { + project.writeDescription(IResource.FORCE); + } catch (CoreException e) { + // a problem happened updating the description, update the description in memory + project.updateDescription(); + throw e; // rethrow + } + } + } + } + + /* if we are synchronizing, do not delete the resource. Convert it + into a phantom. Actual deletion will happen when we refresh or push. */ + if (convertToPhantom && getType() != PROJECT && synchronizing(getResourceInfo(true, false))) + convertToPhantom(); + else + workspace.deleteResource(this); + + List filters = findFilters(); + if ((filters != null) && (filters.size() > 0)) { + // delete resource filters + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + if (description != null) { + for (Iterator it = filters.iterator(); it.hasNext();) + description.setFilters(it.next().getProjectRelativePath(), null); + project.internalSetDescription(description, true); + project.writeDescription(IResource.FORCE); + } + } + + // Delete properties after the resource is deleted from the tree. See bug 84584. + CoreException err = null; + try { + getPropertyManager().deleteResource(this); + } catch (CoreException e) { + if (status != null) + status.add(e.getStatus()); + else + err = e; + } + if (err != null) + throw err; + } + + /* + * Returns a list of all linked resources at or below this resource, or null if there + * are no links. + */ + private List findLinks() { + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + HashMap linkMap = description.getLinks(); + if (linkMap == null) + return null; + List links = null; + IPath myPath = getProjectRelativePath(); + for (Iterator it = linkMap.values().iterator(); it.hasNext();) { + LinkDescription link = it.next(); + IPath linkPath = link.getProjectRelativePath(); + if (myPath.isPrefixOf(linkPath)) { + if (links == null) + links = new ArrayList<>(); + links.add(workspace.newResource(project.getFullPath().append(linkPath), link.getType())); + } + } + return links; + } + + /* + * Returns a list of all filtered resources at or below this resource, or null if there + * are no links. + */ + private List findFilters() { + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + List filters = null; + if (description != null) { + HashMap> filterMap = description.getFilters(); + if (filterMap != null) { + IPath myPath = getProjectRelativePath(); + for (Iterator it = filterMap.keySet().iterator(); it.hasNext();) { + IPath filterPath = it.next(); + if (myPath.isPrefixOf(filterPath)) { + if (filters == null) + filters = new ArrayList<>(); + filters.add(workspace.newResource(project.getFullPath().append(filterPath), IResource.FOLDER)); + } + } + } + } + return filters; + } + + @Override + public boolean equals(Object target) { + if (this == target) + return true; + if (!(target instanceof Resource)) + return false; + Resource resource = (Resource) target; + return getType() == resource.getType() && path.equals(resource.path) && workspace.equals(resource.workspace); + } + + @Override + public boolean exists() { + ResourceInfo info = getResourceInfo(false, false); + return exists(getFlags(info), true); + } + + public boolean exists(int flags, boolean checkType) { + return flags != NULL_FLAG && !(checkType && ResourceInfo.getType(flags) != getType()); + } + + /** + * Helper method for case insensitive file systems. Returns + * an existing resource whose path differs only in case from + * the given path, or null if no such resource exists. + */ + public IResource findExistingResourceVariant(IPath target) { + if (!workspace.tree.includesIgnoreCase(target)) + return null; + //ignore phantoms + ResourceInfo info = (ResourceInfo) workspace.tree.getElementDataIgnoreCase(target); + if (info != null && info.isSet(M_PHANTOM)) + return null; + //resort to slow lookup to find exact case variant + IPath result = Path.ROOT; + int segmentCount = target.segmentCount(); + for (int i = 0; i < segmentCount; i++) { + String[] childNames = workspace.tree.getNamesOfChildren(result); + String name = findVariant(target.segment(i), childNames); + if (name == null) + return null; + result = result.append(name); + } + return workspace.getRoot().findMember(result); + } + + @Override + public IMarker findMarker(long id) { + return workspace.getMarkerManager().findMarker(this, id); + } + + @Override + public IMarker[] findMarkers(String type, boolean includeSubtypes, int depth) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + // It might happen that from this point the resource is not accessible anymore. + // But markers have the #exists method that callers can use to check if it is + // still valid. + return workspace.getMarkerManager().findMarkers(this, type, includeSubtypes, depth); + } + + @Override + public int findMaxProblemSeverity(String type, boolean includeSubtypes, int depth) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + // It might happen that from this point the resource is not accessible anymore. + // But markers have the #exists method that callers can use to check if it is + // still valid. + return workspace.getMarkerManager().findMaxProblemSeverity(this, type, includeSubtypes, depth); + } + + /** + * Searches for a variant of the given target in the list, + * that differs only in case. Returns the variant from + * the list if one is found, otherwise returns null. + */ + private String findVariant(String target, String[] list) { + for (int i = 0; i < list.length; i++) { + if (target.toUpperCase().equals(list[i].toUpperCase())) + return list[i]; + } + return null; + } + + protected void fixupAfterMoveSource() throws CoreException { + ResourceInfo info = getResourceInfo(true, true); + //if a linked resource is moved, we need to remove the location info from the .project + if (isLinked() || isVirtual()) { + Project project = (Project) getProject(); + if (project.internalGetDescription().setLinkLocation(getProjectRelativePath(), null)) + project.writeDescription(IResource.NONE); + } + + List filters = findFilters(); + if ((filters != null) && (filters.size() > 0)) { + // delete resource filters + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + for (Iterator it = filters.iterator(); it.hasNext();) + description.setFilters(it.next().getProjectRelativePath(), null); + project.writeDescription(IResource.NONE); + } + + // check if we deleted a preferences file + ProjectPreferences.deleted(this); + + if (!synchronizing(info)) { + workspace.deleteResource(this); + return; + } + info.clearSessionProperties(); + info.clear(M_LOCAL_EXISTS); + info.setLocalSyncInfo(I_NULL_SYNC_INFO); + info.set(M_PHANTOM); + info.clearModificationStamp(); + info.setMarkers(null); + } + + @Override + public String getFileExtension() { + String name = getName(); + int index = name.lastIndexOf('.'); + if (index == -1) + return null; + if (index == (name.length() - 1)) + return ""; //$NON-NLS-1$ + return name.substring(index + 1); + } + + public int getFlags(ResourceInfo info) { + return (info == null) ? NULL_FLAG : info.getFlags(); + } + + @Override + public IPath getFullPath() { + return path; + } + + public FileSystemResourceManager getLocalManager() { + return workspace.getFileSystemManager(); + } + + @Override + public long getLocalTimeStamp() { + ResourceInfo info = getResourceInfo(false, false); + return (info == null || isVirtual()) ? IResource.NULL_STAMP : info.getLocalSyncInfo(); + } + + @Override + public IPath getLocation() { + IProject project = getProject(); + if (project != null && !project.exists()) + return null; + return getLocalManager().locationFor(this, false); + } + + @Override + public URI getLocationURI() { + IProject project = getProject(); + if (project != null && !project.exists()) + return null; + return getLocalManager().locationURIFor(this, false); + } + + @Override + public IMarker getMarker(long id) { + return new Marker(this, id); + } + + protected MarkerManager getMarkerManager() { + return workspace.getMarkerManager(); + } + + @Override + public long getModificationStamp() { + ResourceInfo info = getResourceInfo(false, false); + return info == null ? IResource.NULL_STAMP : info.getModificationStamp(); + } + + @Override + public String getName() { + return path.lastSegment(); + } + + @Override + public IContainer getParent() { + int segments = path.segmentCount(); + //zero and one segments handled by subclasses + if (segments < 2) + Assert.isLegal(false, path.toString()); + if (segments == 2) + return workspace.getRoot().getProject(path.segment(0)); + return (IFolder) workspace.newResource(path.removeLastSegments(1), IResource.FOLDER); + } + + @Override + public String getPersistentProperty(QualifiedName key) throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + return getPropertyManager().getProperty(this, key); + } + + @Override + public Map getPersistentProperties() throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + return getPropertyManager().getProperties(this); + } + + @Override + public IProject getProject() { + return workspace.getRoot().getProject(path.segment(0)); + } + + @Override + public IPath getProjectRelativePath() { + return getFullPath().removeFirstSegments(ICoreConstants.PROJECT_SEGMENT_LENGTH); + } + + public IPropertyManager getPropertyManager() { + return workspace.getPropertyManager(); + } + + @Override + public IPath getRawLocation() { + if (isLinked()) + return FileUtil.toPath(((Project) getProject()).internalGetDescription().getLinkLocationURI(getProjectRelativePath())); + return getLocation(); + } + + @Override + public URI getRawLocationURI() { + if (isLinked()) + return ((Project) getProject()).internalGetDescription().getLinkLocationURI(getProjectRelativePath()); + return getLocationURI(); + } + + @Override + public ResourceAttributes getResourceAttributes() { + if (!isAccessible() || isVirtual()) + return null; + return getLocalManager().attributes(this); + } + + /** + * Returns the resource info. Returns null if the resource doesn't exist. + * If the phantom flag is true, phantom resources are considered. + * If the mutable flag is true, a mutable info is returned. + */ + public ResourceInfo getResourceInfo(boolean phantom, boolean mutable) { + return workspace.getResourceInfo(getFullPath(), phantom, mutable); + } + + @Override + public Object getSessionProperty(QualifiedName key) throws CoreException { + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + return info.getSessionProperty(key); + } + + @Override + public Map getSessionProperties() throws CoreException { + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + return info.getSessionProperties(); + } + + public IFileStore getStore() { + return getLocalManager().getStore(this); + } + + @Override + public abstract int getType(); + + public String getTypeString() { + switch (getType()) { + case FILE : + return "L"; //$NON-NLS-1$ + case FOLDER : + return "F"; //$NON-NLS-1$ + case PROJECT : + return "P"; //$NON-NLS-1$ + case ROOT : + return "R"; //$NON-NLS-1$ + } + return ""; //$NON-NLS-1$ + } + + @Override + public IWorkspace getWorkspace() { + return workspace; + } + + @Override + public int hashCode() { + // the container may be null if the identified resource + // does not exist so don't bother with it in the hash + return getFullPath().hashCode(); + } + + /** + * Sets the M_LOCAL_EXISTS flag. Is internal so we don't have + * to begin an operation. + */ + protected void internalSetLocal(boolean flag, int depth) throws CoreException { + ResourceInfo info = getResourceInfo(true, true); + //only make the change if it's not already in desired state + if (info.isSet(M_LOCAL_EXISTS) != flag) { + if (flag && !isPhantom(getFlags(info))) { + info.set(M_LOCAL_EXISTS); + workspace.updateModificationStamp(info); + } else { + info.clear(M_LOCAL_EXISTS); + info.clearModificationStamp(); + } + } + if (getType() == IResource.FILE || depth == IResource.DEPTH_ZERO) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.DEPTH_ZERO; + IResource[] children = ((IContainer) this).members(); + for (int i = 0; i < children.length; i++) + ((Resource) children[i]).internalSetLocal(flag, depth); + } + + @Override + public boolean isAccessible() { + return exists(); + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + if (this == rule) + return true; + //must not schedule at same time as notification + if (rule.getClass().equals(WorkManager.NotifyRule.class)) + return true; + if (rule instanceof MultiRule) { + MultiRule multi = (MultiRule) rule; + ISchedulingRule[] children = multi.getChildren(); + for (int i = 0; i < children.length; i++) + if (isConflicting(children[i])) + return true; + return false; + } + if (!(rule instanceof IResource)) + return false; + IResource resource = (IResource) rule; + if (!workspace.equals(resource.getWorkspace())) + return false; + IPath otherPath = resource.getFullPath(); + return path.isPrefixOf(otherPath) || otherPath.isPrefixOf(path); + } + + @Override + public boolean isDerived() { + return isDerived(IResource.NONE); + } + + @Override + public boolean isDerived(int options) { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_DERIVED)) + return true; + // check ancestors if the appropriate option is set + if ((options & CHECK_ANCESTORS) != 0) + return getParent().isDerived(options); + return false; + } + + @Override + public boolean isHidden() { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_HIDDEN); + } + + @Override + public boolean isHidden(int options) { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_HIDDEN)) + return true; + // check ancestors if the appropriate option is set + if ((options & CHECK_ANCESTORS) != 0) + return getParent().isHidden(options); + return false; + } + + @Override + public boolean isLinked() { + return isLinked(NONE); + } + + @Override + public boolean isLinked(int options) { + if ((options & CHECK_ANCESTORS) != 0) { + IProject project = getProject(); + if (project == null) + return false; + ProjectDescription desc = ((Project) project).internalGetDescription(); + if (desc == null) + return false; + HashMap links = desc.getLinks(); + if (links == null) + return false; + IPath myPath = getProjectRelativePath(); + for (Iterator it = links.values().iterator(); it.hasNext();) { + if (it.next().getProjectRelativePath().isPrefixOf(myPath)) + return true; + } + return false; + } + //the no ancestor checking case + ResourceInfo info = getResourceInfo(false, false); + return info != null && info.isSet(M_LINK); + } + + @Override + public boolean isVirtual() { + ResourceInfo info = getResourceInfo(false, false); + return info != null && info.isSet(M_VIRTUAL); + } + + /* + * @return whether the current resource has a parent that is virtual. + */ + public boolean isUnderVirtual() { + IContainer parent = getParent(); + while (parent != null) { + if (parent.isVirtual()) + return true; + parent = parent.getParent(); + } + return false; + } + + @Override + @Deprecated + public boolean isLocal(int depth) { + ResourceInfo info = getResourceInfo(false, false); + return isLocal(getFlags(info), depth); + } + + /** + * Note the depth parameter is intentionally ignored because + * this method is over-ridden by Container.isLocal(). + * @deprecated + */ + @Deprecated + public boolean isLocal(int flags, int depth) { + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_LOCAL_EXISTS); + } + + /** + * Returns whether a resource should be included in a traversal + * based on the provided member flags. + * + * @param flags The resource info flags + * @param memberFlags The member flag mask + * @return Whether the resource is included + */ + protected boolean isMember(int flags, int memberFlags) { + int excludeMask = 0; + if ((memberFlags & IContainer.INCLUDE_PHANTOMS) == 0) + excludeMask |= M_PHANTOM; + if ((memberFlags & IContainer.INCLUDE_HIDDEN) == 0) + excludeMask |= M_HIDDEN; + if ((memberFlags & IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS) == 0) + excludeMask |= M_TEAM_PRIVATE_MEMBER; + if ((memberFlags & IContainer.EXCLUDE_DERIVED) != 0) + excludeMask |= M_DERIVED; + //the resource is a matching member if it matches none of the exclude flags + return flags != NULL_FLAG && (flags & excludeMask) == 0; + } + + @Override + public boolean isPhantom() { + ResourceInfo info = getResourceInfo(true, false); + return isPhantom(getFlags(info)); + } + + public boolean isPhantom(int flags) { + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_PHANTOM); + } + + @Deprecated + @Override + public boolean isReadOnly() { + final ResourceAttributes attributes = getResourceAttributes(); + return attributes == null ? false : attributes.isReadOnly(); + } + + @Override + public boolean isSynchronized(int depth) { + return getLocalManager().isSynchronized(this, depth); + } + + @Override + public boolean isTeamPrivateMember() { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_TEAM_PRIVATE_MEMBER); + } + + @Override + public boolean isTeamPrivateMember(int options) { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_TEAM_PRIVATE_MEMBER)) + return true; + // check ancestors if the appropriate option is set + if ((options & CHECK_ANCESTORS) != 0) + return getParent().isTeamPrivateMember(options); + return false; + } + + /** + * Returns true if this resource is a linked resource, or a child of a linked + * resource, and false otherwise. + */ + public boolean isUnderLink() { + int depth = path.segmentCount(); + if (depth < 2) + return false; + if (depth == 2) + return isLinked(); + //check if parent at depth two is a link + IPath linkParent = path.removeLastSegments(depth - 2); + return workspace.getResourceInfo(linkParent, false, false).isSet(ICoreConstants.M_LINK); + } + + protected IPath makePathAbsolute(IPath target) { + if (target.isAbsolute()) + return target; + return getParent().getFullPath().append(target); + } + + /* (non-Javadoc) + * @see IFile#move(IPath, boolean, boolean, IProgressMonitor) + * @see IFolder#move(IPath, boolean, boolean, IProgressMonitor) + */ + public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; + move(destination, updateFlags, monitor); + } + + @Override + public void move(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + move(destination, force ? IResource.FORCE : IResource.NONE, monitor); + } + + @Override + public void move(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_moving, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + Policy.checkCanceled(monitor); + destination = makePathAbsolute(destination); + checkValidPath(destination, getType(), false); + Resource destResource = workspace.newResource(destination, getType()); + final ISchedulingRule rule = workspace.getRuleFactory().moveRule(this, destResource); + try { + workspace.prepareOperation(rule, monitor); + // The following assert method throws CoreExceptions as stated in the IResource.move API + // and assert for programming errors. See checkMoveRequirements for more information. + assertMoveRequirements(destination, getType(), updateFlags); + workspace.beginOperation(true); + broadcastPreMoveEvent(destResource, updateFlags); + IFileStore originalStore = getStore(); + message = Messages.resources_moveProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, null); + WorkManager workManager = workspace.getWorkManager(); + ResourceTree tree = new ResourceTree(workspace.getFileSystemManager(), workManager.getLock(), status, updateFlags); + boolean success = false; + int depth = 0; + try { + depth = workManager.beginUnprotected(); + success = unprotectedMove(tree, destResource, updateFlags, monitor); + } finally { + workManager.endUnprotected(depth); + } + // Invalidate the tree for further use by clients. + tree.makeInvalid(); + //update any aliases of this resource and the destination + if (success) { + workspace.getAliasManager().updateAliases(this, originalStore, IResource.DEPTH_INFINITE, monitor); + workspace.getAliasManager().updateAliases(destResource, destResource.getStore(), IResource.DEPTH_INFINITE, monitor); + } + if (!tree.getStatus().isOK()) + throw new ResourceException(tree.getStatus()); + // if this is a project, make sure the move operation is remembered + if (getType() == PROJECT) + workspace.getSaveManager().requestSnapshot(); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void move(IProjectDescription description, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; + move(description, updateFlags, monitor); + } + + @Override + public void move(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(description); + if (getType() != IResource.PROJECT) { + String message = NLS.bind(Messages.resources_moveNotProject, getFullPath(), description.getName()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + ((Project) this).move(description, updateFlags, monitor); + } + + @Override + public void refreshLocal(int depth, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + boolean isRoot = getType() == ROOT; + String message = isRoot ? Messages.resources_refreshingRoot : NLS.bind(Messages.resources_refreshing, getFullPath()); + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + monitor.subTask(message); + boolean build = false; + final ISchedulingRule rule = workspace.getRuleFactory().refreshRule(this); + try { + workspace.prepareOperation(rule, monitor); + if (!isRoot && !getProject().isAccessible()) + return; + if (!exists() && isFiltered()) + return; + workspace.beginOperation(true); + if (getType() == IResource.PROJECT || getType() == IResource.ROOT) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_REFRESH, this)); + build = getLocalManager().refresh(this, depth, true, Policy.subMonitorFor(monitor, Policy.opWork)); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, build, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public String requestName() { + return getName(); + } + + @Override + public IPath requestPath() { + return getFullPath(); + } + + @Override + public void revertModificationStamp(long value) throws CoreException { + if (value < 0) + throw new IllegalArgumentException("Illegal value: " + value); //$NON-NLS-1$ + // fetch the info but don't bother making it mutable even though we are going + // to modify it. It really doesn't matter as the change we are doing does not show up in deltas. + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + info.setModificationStamp(value); + } + + @Deprecated + @Override + public void setDerived(boolean isDerived) throws CoreException { + // fetch the info but don't bother making it mutable even though we are going + // to modify it. We don't know whether or not the tree is open and it really doesn't + // matter as the change we are doing does not show up in deltas. + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + // ignore attempts to set derived flag on anything except files and folders + if (info.getType() == FILE || info.getType() == FOLDER) { + if (isDerived) { + info.set(ICoreConstants.M_DERIVED); + } else { + info.clear(ICoreConstants.M_DERIVED); + } + } + } + + @Override + public void setDerived(boolean isDerived, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_settingDerivedFlag, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + final ISchedulingRule rule = workspace.getRuleFactory().derivedRule(this); + try { + workspace.prepareOperation(rule, monitor); + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + // ignore attempts to set derived flag on anything except files and folders + if (info.getType() != FILE && info.getType() != FOLDER) + return; + workspace.beginOperation(true); + info = getResourceInfo(false, true); + if (isDerived) + info.set(ICoreConstants.M_DERIVED); + else { + info.clear(ICoreConstants.M_DERIVED); + } + 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 setHidden(boolean isHidden) throws CoreException { + // fetch the info but don't bother making it mutable even though we are going + // to modify it. We don't know whether or not the tree is open and it really doesn't + // matter as the change we are doing does not show up in deltas. + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + if (isHidden) { + info.set(ICoreConstants.M_HIDDEN); + } else { + info.clear(ICoreConstants.M_HIDDEN); + } + } + + @Deprecated + @Override + public void setLocal(boolean flag, int depth, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = Messages.resources_setLocal; + monitor.beginTask(message, Policy.totalWork); + try { + workspace.prepareOperation(null, monitor); + workspace.beginOperation(true); + internalSetLocal(flag, depth); + monitor.worked(Policy.opWork); + } finally { + workspace.endOperation(null, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public long setLocalTimeStamp(long value) throws CoreException { + if (value < 0) + throw new IllegalArgumentException("Illegal value: " + value); //$NON-NLS-1$ + // fetch the info but don't bother making it mutable even though we are going + // to modify it. It really doesn't matter as the change we are doing does not show up in deltas. + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + return getLocalManager().setLocalTimeStamp(this, info, value); + } + + @Override + public void setPersistentProperty(QualifiedName key, String value) throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + getPropertyManager().setProperty(this, key, value); + } + + @Deprecated + @Override + public void setReadOnly(boolean readonly) { + ResourceAttributes attributes = getResourceAttributes(); + if (attributes == null) + return; + attributes.setReadOnly(readonly); + try { + setResourceAttributes(attributes); + } catch (CoreException e) { + //failure is not an option + } + } + + @Override + public void setResourceAttributes(ResourceAttributes attributes) throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + getLocalManager().setResourceAttributes(this, attributes); + } + + @Override + public void setSessionProperty(QualifiedName key, Object value) throws CoreException { + // fetch the info but don't bother making it mutable even though we are going + // to modify it. We don't know whether or not the tree is open and it really doesn't + // matter as the change we are doing does not show up in deltas. + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + info.setSessionProperty(key, value); + } + + @Override + public void setTeamPrivateMember(boolean isTeamPrivate) throws CoreException { + // fetch the info but don't bother making it mutable even though we are going + // to modify it. We don't know whether or not the tree is open and it really doesn't + // matter as the change we are doing does not show up in deltas. + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + // ignore attempts to set team private member flag on anything except files and folders + if (info.getType() == FILE || info.getType() == FOLDER) { + if (isTeamPrivate) { + info.set(ICoreConstants.M_TEAM_PRIVATE_MEMBER); + } else { + info.clear(ICoreConstants.M_TEAM_PRIVATE_MEMBER); + } + } + } + + /** + * Returns true if this resource has the potential to be + * (or have been) synchronized. + */ + public boolean synchronizing(ResourceInfo info) { + return info != null && info.getSyncInfo(false) != null; + } + + @Override + public String toString() { + return getTypeString() + getFullPath().toString(); + } + + @Override + public void touch(IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_touch, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, monitor); + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + + workspace.beginOperation(true); + // fake a change by incrementing the content ID + info = getResourceInfo(false, true); + info.incrementContentId(); + // forget content-related caching flags + info.clear(M_CONTENT_CACHE); + workspace.updateModificationStamp(info); + 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(); + } + } + + /** + * Calls the move/delete hook to perform the deletion. Since this method calls + * client code, it is run "unprotected", so the workspace lock is not held. + */ + private void unprotectedDelete(ResourceTree tree, int updateFlags, IProgressMonitor monitor) { + IMoveDeleteHook hook = workspace.getMoveDeleteHook(); + switch (getType()) { + case IResource.FILE : + if (!hook.deleteFile(tree, (IFile) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000 / 2))) + tree.standardDeleteFile((IFile) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000)); + break; + case IResource.FOLDER : + if (!hook.deleteFolder(tree, (IFolder) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000 / 2))) + tree.standardDeleteFolder((IFolder) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000)); + break; + case IResource.PROJECT : + if (!hook.deleteProject(tree, (IProject) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000 / 2))) + tree.standardDeleteProject((IProject) this, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000)); + break; + case IResource.ROOT : + // when the root is deleted, all its children including hidden projects + // have to be deleted + IProject[] projects = ((IWorkspaceRoot) this).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) { + if (!hook.deleteProject(tree, projects[i], updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000 / projects.length / 2))) + tree.standardDeleteProject(projects[i], updateFlags, Policy.subMonitorFor(monitor, Policy.opWork * 1000 / projects.length)); + } + } + } + + /** + * Calls the move/delete hook to perform the move. Since this method calls + * client code, it is run "unprotected", so the workspace lock is not held. + * Returns true if resources were actually moved, and false otherwise. + */ + private boolean unprotectedMove(ResourceTree tree, final IResource destination, int updateFlags, IProgressMonitor monitor) throws CoreException, ResourceException { + IMoveDeleteHook hook = workspace.getMoveDeleteHook(); + switch (getType()) { + case IResource.FILE : + if (!hook.moveFile(tree, (IFile) this, (IFile) destination, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2))) + tree.standardMoveFile((IFile) this, (IFile) destination, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork)); + break; + case IResource.FOLDER : + if (!hook.moveFolder(tree, (IFolder) this, (IFolder) destination, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2))) + tree.standardMoveFolder((IFolder) this, (IFolder) destination, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork)); + break; + case IResource.PROJECT : + IProject project = (IProject) this; + // if there is no change in name, there is nothing to do so return. + if (getName().equals(destination.getName())) + return false; + IProjectDescription description = project.getDescription(); + description.setName(destination.getName()); + if (!hook.moveProject(tree, project, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2))) + tree.standardMoveProject(project, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork)); + break; + case IResource.ROOT : + String msg = Messages.resources_moveRoot; + throw new ResourceException(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), msg)); + } + return true; + } + + private void broadcastPreDeleteEvent() throws CoreException { + switch (getType()) { + case IResource.PROJECT : + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_DELETE, this)); + break; + case IResource.ROOT : + // all root children including hidden projects will be deleted so notify + IResource[] projects = ((Container) this).getChildren(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_DELETE, projects[i])); + } + } + + private void broadcastPreMoveEvent(final IResource destination, int updateFlags) throws CoreException { + switch (getType()) { + case IResource.FILE : + if (isLinked()) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_MOVE, this, destination, updateFlags)); + break; + case IResource.FOLDER : + if (isLinked()) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_MOVE, this, destination, updateFlags)); + if (isVirtual()) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_GROUP_MOVE, this, destination, updateFlags)); + break; + case IResource.PROJECT : + if (!getName().equals(destination.getName())) { + // if there is a change in name, we are deleting the source project so notify. + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_MOVE, this, destination, updateFlags)); + } + break; + } + } + + @Override + public IPathVariableManager getPathVariableManager() { + if (getProject() == null) + return workspace.getPathVariableManager(); + return new ProjectPathVariableManager(this); + } + + /* (non-Javadoc) + * Calculates whether the current resource is filtered out from the resource tree + * by resource filters. This can happen because resource filters apply to the resource, + * or because resource filters apply to one of its parent. For example, if "/foo/bar" + * is filtered out, then calling isFilteredFromParent() on "/foo/bar/sub/file.txt" will + * return true as well, even though there's no resource filters that apply to "file.txt" per se. + * + * @return true is the resource is filtered out from the resource tree + * @see IResource#isFiltered() + */ + public boolean isFiltered() { + try { + return isFilteredWithException(false); + } catch (CoreException e) { + // nothing + } + return false; + } + + public boolean isFilteredWithException(boolean throwExeception) throws CoreException { + if (isLinked() || isVirtual()) + return false; + + Project project = (Project) getProject(); + if (project == null) + return false; + final ProjectDescription description = project.internalGetDescription(); + if (description == null) + return false; + if (description.getFilters() == null) + return false; + + Resource currentResource = this; + while (currentResource != null && currentResource.getParent() != null) { + Resource parent = (Resource) currentResource.getParent(); + IFileStore store = currentResource.getStore(); + if (store != null) { + FileInfo fileInfo = new FileInfo(store.getName()); + fileInfo.setDirectory(currentResource.getType() == IResource.FOLDER); + if (fileInfo != null) { + IFileInfo[] filtered = parent.filterChildren(project, description, new IFileInfo[] {fileInfo}, throwExeception); + if (filtered.length == 0) + return true; + } + } + currentResource = parent; + } + return false; + } + + public IFileInfo[] filterChildren(IFileInfo[] list, boolean throwException) throws CoreException { + Project project = (Project) getProject(); + if (project == null) + return list; + final ProjectDescription description = project.internalGetDescription(); + if (description == null) + return list; + return filterChildren(project, description, list, throwException); + } + + private IFileInfo[] filterChildren(Project project, ProjectDescription description, IFileInfo[] list, boolean throwException) throws CoreException { + IPath relativePath = getProjectRelativePath(); + LinkedList currentIncludeFilters = new LinkedList<>(); + LinkedList currentExcludeFilters = new LinkedList<>(); + LinkedList filters = null; + + boolean firstSegment = true; + do { + if (!firstSegment) + relativePath = relativePath.removeLastSegments(1); + filters = description.getFilter(relativePath); + if (filters != null) { + for (Iterator it = filters.iterator(); it.hasNext();) { + FilterDescription desc = it.next(); + if (firstSegment || desc.isInheritable()) { + Filter filter = new Filter(project, desc); + if (filter.isIncludeOnly()) { + if (filter.isFirst()) + currentIncludeFilters.addFirst(filter); + else + currentIncludeFilters.addLast(filter); + } else { + if (filter.isFirst()) + currentExcludeFilters.addFirst(filter); + else + currentExcludeFilters.addLast(filter); + } + } + } + } + firstSegment = false; + } while (relativePath.segmentCount() > 0); + + if ((currentIncludeFilters.size() > 0) || (currentExcludeFilters.size() > 0)) { + try { + list = Filter.filter(project, currentIncludeFilters, currentExcludeFilters, (IContainer) this, list); + } catch (CoreException e) { + if (throwException) + throw e; + } + } + return list; + } + + /* + * (non-Javadoc) + * + * @see IResource#setLinkLocation(IPath) + */ + public void setLinkLocation(URI location, int updateFlags, IProgressMonitor monitor) throws CoreException { + if (!isLinked()) { + String message = NLS.bind(Messages.links_resourceIsNotALink, getFullPath()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + + final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); + try { + String message = NLS.bind(Messages.links_setLocation, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + + workspace.prepareOperation(rule, monitor); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_CHANGE, this)); + workspace.beginOperation(true); + + ResourceInfo info = workspace.getResourceInfo(getFullPath(), true, false); + getLocalManager().setLocation(this, info, location); + + LinkDescription linkDescription; + linkDescription = new LinkDescription(this, location); + Project project = (Project) getProject(); + project.internalGetDescription().setLinkLocation(getProjectRelativePath(), linkDescription); + project.writeDescription(updateFlags); + + // 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)); + } + } finally { + workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } + + /* + * (non-Javadoc) + * + * @see IResource#setLinkLocation(URI) + */ + public void setLinkLocation(IPath location, int updateFlags, IProgressMonitor monitor) throws CoreException { + if (location.isAbsolute()) { + setLinkLocation(URIUtil.toURI(location.toPortableString()), updateFlags, monitor); + } else { + try { + setLinkLocation(new URI(null, null, location.toPortableString(), null), updateFlags, monitor); + } catch (URISyntaxException e) { + setLinkLocation(URIUtil.toURI(location.toPortableString()), updateFlags, monitor); + } + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java new file mode 100644 index 0000000000..288ac8c454 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * 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 java.io.PrintStream; +import java.io.PrintWriter; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; + +/** + * A checked exception representing a failure. + *

          + * Resource exceptions contain a status object describing the cause of the + * exception, and optionally the path of the resource where the failure + * occurred. + *

          + * + * @see IStatus + */ +public class ResourceException extends CoreException { + /** + * All serializable objects should have a stable serialVersionUID + */ + private static final long serialVersionUID = 1L; + + public ResourceException(int code, IPath path, String message, Throwable exception) { + super(new ResourceStatus(code, path, message, exception)); + } + + /** + * Constructs a new exception with the given status object. + * + * @param status the status object to be associated with this exception + * @see IStatus + */ + public ResourceException(IStatus status) { + super(status); + } + + /** + * Prints a stack trace out for the exception, and + * any nested exception that it may have embedded in + * its Status object. + */ + @Override + public void printStackTrace() { + printStackTrace(System.err); + } + + /** + * Prints a stack trace out for the exception, and + * any nested exception that it may have embedded in + * its Status object. + */ + @Override + public void printStackTrace(PrintStream output) { + synchronized (output) { + IStatus status = getStatus(); + if (status.getException() != null) { + String path = "()"; //$NON-NLS-1$ + if (status instanceof IResourceStatus) + path = "(" + ((IResourceStatus) status).getPath() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + output.print(getClass().getName() + path + "[" + status.getCode() + "]: "); //$NON-NLS-1$ //$NON-NLS-2$ + status.getException().printStackTrace(output); + } else + super.printStackTrace(output); + } + } + + /** + * Prints a stack trace out for the exception, and + * any nested exception that it may have embedded in + * its Status object. + */ + @Override + public void printStackTrace(PrintWriter output) { + synchronized (output) { + IStatus status = getStatus(); + if (status.getException() != null) { + String path = "()"; //$NON-NLS-1$ + if (status instanceof IResourceStatus) + path = "(" + ((IResourceStatus) status).getPath() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + output.print(getClass().getName() + path + "[" + status.getCode() + "]: "); //$NON-NLS-1$ //$NON-NLS-2$ + status.getException().printStackTrace(output); + } else + super.printStackTrace(output); + } + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java new file mode 100644 index 0000000000..a421544944 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java @@ -0,0 +1,477 @@ +/******************************************************************************* + * 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 + * Oakland Software Incorporated - added getSessionProperties and getPersistentProperties + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.Map; +import org.eclipse.core.internal.localstore.FileStoreRoot; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.internal.watson.IElementTreeData; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.QualifiedName; + +/** + * A data structure containing the in-memory state of a resource in the workspace. + */ +public class ResourceInfo implements IElementTreeData, ICoreConstants, IStringPoolParticipant { + protected static final int LOWER = 0xFFFF; + protected static final int UPPER = 0xFFFF0000; + + /** + * This field stores the resource modification stamp in the lower two bytes, + * and the character set generation count in the higher two bytes. + */ + protected volatile int charsetAndContentId = 0; + + /** + * The file system root that this resource is stored in + */ + protected FileStoreRoot fileStoreRoot; + + /** Set of flags which reflect various states of the info (used, derived, ...). */ + protected int flags = 0; + + /** Local sync info */ + // thread safety: (Concurrency004) + protected volatile long localInfo = I_NULL_SYNC_INFO; + + /** + * This field stores the sync info generation in the lower two bytes, and + * the marker generation count in the upper two bytes. + */ + protected volatile int markerAndSyncStamp; + + /** The collection of markers for this resource. */ + protected MarkerSet markers = null; + + /** Modification stamp */ + protected long modStamp = 0; + + /** Unique node identifier */ + // thread safety: (Concurrency004) + protected volatile long nodeId = 0; + + /** + * The properties which are maintained for the lifecycle of the workspace. + *

          + * This field is declared as the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + protected ObjectMap sessionProperties = null; + + /** + * The table of sync information. + *

          + * This field is declared as the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + protected ObjectMap syncInfo = null; + + /** + * Returns the integer value stored in the indicated part of this info's flags. + */ + protected static int getBits(int flags, int mask, int start) { + return (flags & mask) >> start; + } + + /** + * Returns the type setting for this info. Valid values are + * FILE, FOLDER, PROJECT, + */ + public static int getType(int flags) { + return getBits(flags, M_TYPE, M_TYPE_START); + } + + /** + * Returns true if all of the bits indicated by the mask are set. + */ + public static boolean isSet(int flags, int mask) { + return (flags & mask) == mask; + } + + /** + * Clears all of the bits indicated by the mask. + */ + public void clear(int mask) { + flags &= ~mask; + } + + public void clearModificationStamp() { + modStamp = IResource.NULL_STAMP; + } + + public synchronized void clearSessionProperties() { + sessionProperties = null; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; // never gets here. + } + } + + public int getCharsetGenerationCount() { + return charsetAndContentId >> 16; + } + + public int getContentId() { + return charsetAndContentId & LOWER; + } + + public FileStoreRoot getFileStoreRoot() { + return fileStoreRoot; + } + + /** + * Returns the set of flags for this info. + */ + public int getFlags() { + return flags; + } + + /** + * Gets the local-relative sync information. + */ + public long getLocalSyncInfo() { + return localInfo; + } + + /** + * Returns the marker generation count. + * The count is incremented whenever markers on the resource change. + */ + public int getMarkerGenerationCount() { + return markerAndSyncStamp >> 16; + } + + /** + * Returns a copy of the collection of makers on this resource. + * null is returned if there are none. + */ + public MarkerSet getMarkers() { + return getMarkers(true); + } + + /** + * Returns the collection of makers on this resource. + * null is returned if there are none. + */ + public MarkerSet getMarkers(boolean makeCopy) { + if (markers == null) + return null; + return makeCopy ? (MarkerSet) markers.clone() : markers; + } + + public long getModificationStamp() { + return modStamp; + } + + public long getNodeId() { + return nodeId; + } + + /** + * Returns the property store associated with this info. The return value may be null. + */ + public Object getPropertyStore() { + return null; + } + + /** + * Returns a copy of the map of this resource session properties. + * An empty map is returned if there are none. + */ + @SuppressWarnings({"unchecked"}) + public Map getSessionProperties() { + // thread safety: (Concurrency001) + ObjectMap temp = sessionProperties; + if (temp == null) + temp = new ObjectMap<>(5); + else + temp = (ObjectMap) sessionProperties.clone(); + return temp; + } + + /** + * Returns the value of the identified session property + */ + public Object getSessionProperty(QualifiedName name) { + // thread safety: (Concurrency001) + Map temp = sessionProperties; + if (temp == null) + return null; + return temp.get(name); + } + + /** + * The parameter to this method is the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + @SuppressWarnings({"unchecked"}) + public synchronized ObjectMap getSyncInfo(boolean makeCopy) { + if (syncInfo == null) + return null; + return makeCopy ? (ObjectMap) syncInfo.clone() : syncInfo; + } + + public synchronized byte[] getSyncInfo(QualifiedName id, boolean makeCopy) { + // thread safety: (Concurrency001) + byte[] b; + if (syncInfo == null) + return null; + b = (byte[]) syncInfo.get(id); + return b == null ? null : (makeCopy ? (byte[]) b.clone() : b); + } + + /** + * Returns the sync information generation count. + * The count is incremented whenever sync info on the resource changes. + */ + public int getSyncInfoGenerationCount() { + return markerAndSyncStamp & LOWER; + } + + /** + * Returns the type setting for this info. Valid values are + * FILE, FOLDER, PROJECT, + */ + public int getType() { + return getType(flags); + } + + /** + * Increments the charset generation count. + * The count is incremented whenever the encoding on the resource changes. + */ + public void incrementCharsetGenerationCount() { + //increment high order bits + charsetAndContentId = ((charsetAndContentId + LOWER + 1) & UPPER) + (charsetAndContentId & LOWER); + } + + /** + * Mark this resource info as having changed content + */ + public void incrementContentId() { + //increment low order bits + charsetAndContentId = (charsetAndContentId & UPPER) + ((charsetAndContentId + 1) & LOWER); + } + + /** + * Increments the marker generation count. + * The count is incremented whenever markers on the resource change. + */ + public void incrementMarkerGenerationCount() { + //increment high order bits + markerAndSyncStamp = ((markerAndSyncStamp + LOWER + 1) & UPPER) + (markerAndSyncStamp & LOWER); + } + + /** + * Change the modification stamp to indicate that this resource has changed. + * The exact value of the stamp doesn't matter, as long as it can be used to + * distinguish two arbitrary resource generations. + */ + public void incrementModificationStamp() { + modStamp++; + } + + /** + * Increments the sync information generation count. + * The count is incremented whenever sync info on the resource changes. + */ + public void incrementSyncInfoGenerationCount() { + //increment low order bits + markerAndSyncStamp = (markerAndSyncStamp & UPPER) + ((markerAndSyncStamp + 1) & LOWER); + } + + /** + * Returns true if all of the bits indicated by the mask are set. + */ + public boolean isSet(int mask) { + return (flags & mask) == mask; + } + + public void readFrom(int newFlags, DataInput input) throws IOException { + // The flags for this info are read by the visitor (flattener). + // See Workspace.readElement(). This allows the reader to look ahead + // and see what type of info is being loaded. + this.flags = newFlags; + localInfo = input.readLong(); + nodeId = input.readLong(); + charsetAndContentId = input.readInt() & LOWER; + modStamp = input.readLong(); + } + + /** + * Sets all of the bits indicated by the mask. + */ + public void set(int mask) { + flags |= mask; + } + + /** + * Sets the value of the indicated bits to be the given value. + */ + protected void setBits(int mask, int start, int value) { + int baseMask = mask >> start; + int newValue = (value & baseMask) << start; + // thread safety: (guarantee atomic assignment) + int temp = flags; + temp &= ~mask; + temp |= newValue; + flags = temp; + } + + public void setFileStoreRoot(FileStoreRoot fileStoreRoot) { + this.fileStoreRoot = fileStoreRoot; + } + + /** + * Sets the flags for this info. + */ + protected void setFlags(int value) { + flags = value; + } + + /** + * Sets the local-relative sync information. + */ + public void setLocalSyncInfo(long info) { + localInfo = info; + } + + /** + * Sets the collection of makers for this resource. + * null is passed in if there are no markers. + */ + public void setMarkers(MarkerSet value) { + markers = value; + } + + /** + * Sets the resource modification stamp. + */ + public void setModificationStamp(long value) { + this.modStamp = value; + } + + /** + * + */ + public void setNodeId(long id) { + nodeId = id; + // Resource modification stamp starts from current nodeId + // so future generations are distinguishable (bug 160728) + if (modStamp == 0) + modStamp = nodeId; + } + + /** + * Sets the property store associated with this info. The value may be null. + */ + public void setPropertyStore(Object value) { + // needs to be implemented on subclasses + } + + /** + * Sets the identified session property to the given value. If + * the value is null, the property is removed. + */ + @SuppressWarnings({"unchecked"}) + public synchronized void setSessionProperty(QualifiedName name, Object value) { + // thread safety: (Concurrency001) + if (value == null) { + if (sessionProperties == null) + return; + ObjectMap temp = (ObjectMap) sessionProperties.clone(); + temp.remove(name); + if (temp.isEmpty()) + sessionProperties = null; + else + sessionProperties = temp; + } else { + ObjectMap temp = sessionProperties; + if (temp == null) + temp = new ObjectMap<>(5); + else + temp = (ObjectMap) sessionProperties.clone(); + temp.put(name, value); + sessionProperties = temp; + } + } + + /** + * The parameter to this method is the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + protected void setSyncInfo(ObjectMap syncInfo) { + this.syncInfo = syncInfo; + } + + public synchronized void setSyncInfo(QualifiedName id, byte[] value) { + if (value == null) { + //delete sync info + if (syncInfo == null) + return; + syncInfo.remove(id); + if (syncInfo.isEmpty()) + syncInfo = null; + } else { + //add sync info + if (syncInfo == null) + syncInfo = new ObjectMap<>(5); + syncInfo.put(id, value.clone()); + } + } + + /** + * Sets the type for this info to the given value. Valid values are + * FILE, FOLDER, PROJECT + */ + public void setType(int value) { + setBits(M_TYPE, M_TYPE_START, value); + } + + /* (non-Javadoc + * Method declared on IStringPoolParticipant + */ + @Override + public void shareStrings(StringPool set) { + ObjectMap map = syncInfo; + if (map != null) + map.shareStrings(set); + map = sessionProperties; + if (map != null) + map.shareStrings(set); + MarkerSet markerSet = markers; + if (markerSet != null) + markerSet.shareStrings(set); + } + + public void writeTo(DataOutput output) throws IOException { + // The flags for this info are written by the visitor (flattener). + // See SaveManager.writeElement(). This allows the reader to look ahead + // and see what type of info is being loaded. + output.writeLong(localInfo); + output.writeLong(nodeId); + output.writeInt(getContentId()); + output.writeLong(modStamp); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java new file mode 100644 index 0000000000..6c1ba9a456 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.internal.watson.IPathRequestor; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.QualifiedName; + +/** + * Implements a resource proxy given a path requestor and the resource + * info of the resource currently being visited. + */ +public class ResourceProxy implements IResourceProxy, ICoreConstants { + protected final Workspace workspace = (Workspace) ResourcesPlugin.getWorkspace(); + protected IPathRequestor requestor; + protected ResourceInfo info; + + //cached info + protected IPath fullPath; + protected IResource resource; + + /** + * @see org.eclipse.core.resources.IResourceProxy#getModificationStamp() + */ + @Override + public long getModificationStamp() { + return info.getModificationStamp(); + } + + @Override + public String getName() { + return requestor.requestName(); + } + + @Override + public Object getSessionProperty(QualifiedName key) { + return info.getSessionProperty(key); + } + + @Override + public int getType() { + return info.getType(); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isAccessible() + */ + @Override + public boolean isAccessible() { + int flags = info.getFlags(); + if (info.getType() == IResource.PROJECT) + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_OPEN); + return flags != NULL_FLAG; + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isDerived() + */ + @Override + public boolean isDerived() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_DERIVED); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isLinked() + */ + @Override + public boolean isLinked() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_LINK); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isPhantom() + */ + @Override + public boolean isPhantom() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_PHANTOM); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isTeamPrivateMember() + */ + @Override + public boolean isTeamPrivateMember() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_TEAM_PRIVATE_MEMBER); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isHidden() + */ + @Override + public boolean isHidden() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_HIDDEN); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#requestFullPath() + */ + @Override + public IPath requestFullPath() { + if (fullPath == null) + fullPath = requestor.requestPath(); + return fullPath; + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#requestResource() + */ + @Override + public IResource requestResource() { + if (resource == null) + resource = workspace.newResource(requestFullPath(), info.getType()); + return resource; + } + + protected void reset() { + fullPath = null; + resource = null; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java new file mode 100644 index 0000000000..93bdb4c676 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; + +/** + * + */ +public class ResourceStatus extends Status implements IResourceStatus { + IPath path; + + public ResourceStatus(int type, int code, IPath path, String message, Throwable exception) { + super(type, ResourcesPlugin.PI_RESOURCES, code, message, exception); + this.path = path; + } + + public ResourceStatus(int code, String message) { + this(getSeverity(code), code, null, message, null); + } + + public ResourceStatus(int code, IPath path, String message) { + this(getSeverity(code), code, path, message, null); + } + + public ResourceStatus(int code, IPath path, String message, Throwable exception) { + this(getSeverity(code), code, path, message, exception); + } + + /** + * @see IResourceStatus#getPath() + */ + @Override + public IPath getPath() { + return path; + } + + protected static int getSeverity(int code) { + return code == 0 ? 0 : 1 << (code % 100 / 33); + } + + // for debug only + private String getTypeName() { + switch (getSeverity()) { + case IStatus.OK : + return "OK"; //$NON-NLS-1$ + case IStatus.ERROR : + return "ERROR"; //$NON-NLS-1$ + case IStatus.INFO : + return "INFO"; //$NON-NLS-1$ + case IStatus.WARNING : + return "WARNING"; //$NON-NLS-1$ + default : + return String.valueOf(getSeverity()); + } + } + + // for debug only + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("[type: "); //$NON-NLS-1$ + sb.append(getTypeName()); + sb.append("], [path: "); //$NON-NLS-1$ + sb.append(getPath()); + sb.append("], [message: "); //$NON-NLS-1$ + sb.append(getMessage()); + sb.append("], [plugin: "); //$NON-NLS-1$ + sb.append(getPlugin()); + sb.append("], [exception: "); //$NON-NLS-1$ + sb.append(getException()); + sb.append("]\n"); //$NON-NLS-1$ + return sb.toString(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java new file mode 100644 index 0000000000..bba0106b19 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java @@ -0,0 +1,1165 @@ +/******************************************************************************* + * 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] Project Path Variable Support + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.localstore.FileSystemResourceManager; +import org.eclipse.core.internal.properties.IPropertyManager; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IResourceTree; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ILock; +import org.eclipse.osgi.util.NLS; + +/** + * @since 2.0 + * + * Implementation note: Since the move/delete hook involves running third + * party code, the workspace lock is not held. This means the workspace + * lock must be re-acquired whenever we need to manipulate the workspace + * in any way. All entry points from third party code back into the tree must + * be done in an acquire/release pair. + */ +class ResourceTree implements IResourceTree { + private boolean isValid = true; + private final FileSystemResourceManager localManager; + /** + * The lock to acquire when the workspace needs to be manipulated + */ + private ILock lock; + private MultiStatus multistatus; + private int updateFlags; + + /** + * Constructor for this class. + */ + public ResourceTree(FileSystemResourceManager localManager, ILock lock, MultiStatus status, int updateFlags) { + super(); + this.localManager = localManager; + this.lock = lock; + this.multistatus = status; + this.updateFlags = updateFlags; + } + + /** + * @see IResourceTree#addToLocalHistory(IFile) + */ + @Override + public void addToLocalHistory(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + if (!file.exists()) + return; + IFileStore store = localManager.getStore(file); + final IFileInfo fileInfo = store.fetchInfo(); + if (!fileInfo.exists()) + return; + localManager.getHistoryStore().addState(file.getFullPath(), store, fileInfo, false); + } finally { + lock.release(); + } + } + + private IFileStore computeDestinationStore(IProjectDescription destDescription) throws CoreException { + URI destLocation = destDescription.getLocationURI(); + // Use the default area if necessary for the destination. + if (destLocation == null) { + IPath rootLocation = ResourcesPlugin.getWorkspace().getRoot().getLocation(); + destLocation = rootLocation.append(destDescription.getName()).toFile().toURI(); + } + return EFS.getStore(destLocation); + } + + /** + * @see IResourceTree#computeTimestamp(IFile) + */ + @Override + public long computeTimestamp(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + if (!file.getProject().exists()) + return NULL_TIMESTAMP; + return internalComputeTimestamp(file); + } finally { + lock.release(); + } + } + + /** + * Copies the local history of source to destination. Note that if source + * is an IFolder, it is assumed that the same structure exists under destination + * and the local history of any IFile under source will be copied to the + * associated IFile under destination. + */ + private void copyLocalHistory(IResource source, IResource destination) { + localManager.getHistoryStore().copyHistory(source, destination, true); + } + + /** + * @see IResourceTree#deletedFile(IFile) + */ + @Override + public void deletedFile(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!file.exists()) + return; + try { + // Delete properties, generate marker deltas, and remove the node from the workspace tree. + ((Resource) file).deleteResource(true, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorDeleting, file.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, file.getFullPath(), message, e); + failed(status); + } + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#deletedFolder(IFolder) + */ + @Override + public void deletedFolder(IFolder folder) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!folder.exists()) + return; + try { + // Delete properties, generate marker deltas, and remove the node from the workspace tree. + ((Resource) folder).deleteResource(true, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorDeleting, folder.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, folder.getFullPath(), message, e); + failed(status); + } + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#deletedProject(IProject) + */ + @Override + public void deletedProject(IProject target) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!target.exists()) + return; + // Delete properties, generate marker deltas, and remove the node from the workspace tree. + try { + ((Project) target).deleteResource(false, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorDeleting, target.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, target.getFullPath(), message, e); + // log the status but don't return until we try and delete the rest of the project info + failed(status); + } + } finally { + lock.release(); + } + } + + /** + * Makes sure that the destination directory for a project move is unoccupied. + * Returns true if successful, and false if the move should be aborted + */ + private boolean ensureDestinationEmpty(IProject source, IFileStore destinationStore, IProgressMonitor monitor) throws CoreException { + String message; + //Make sure the destination location is unoccupied + if (!destinationStore.fetchInfo().exists()) + return true; + //check for existing children + if (destinationStore.childNames(EFS.NONE, Policy.subMonitorFor(monitor, 0)).length > 0) { + //allow case rename to proceed + if (((Resource) source).getStore().equals(destinationStore)) + return true; + //fail because the destination is occupied + message = NLS.bind(Messages.localstore_resourceExists, destinationStore); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, null); + failed(status); + return false; + } + //delete the destination directory to allow for efficient renaming + destinationStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, 0)); + return true; + } + + /** + * This operation has failed for the given reason. Add it to this + * resource tree's status. + */ + @Override + public void failed(IStatus reason) { + Assert.isLegal(isValid); + multistatus.add(reason); + } + + /** + * Returns the status object held onto by this resource tree. + */ + protected IStatus getStatus() { + return multistatus; + } + + /** + * @see IResourceTree#getTimestamp(IFile) + */ + @Override + public long getTimestamp(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + if (!file.exists()) + return NULL_TIMESTAMP; + ResourceInfo info = ((File) file).getResourceInfo(false, false); + return info == null ? NULL_TIMESTAMP : info.getLocalSyncInfo(); + } finally { + lock.release(); + } + } + + /** + * Returns the local timestamp for a file. + * + * @param file + * @return The local file system timestamp + */ + private long internalComputeTimestamp(IFile file) { + IFileInfo fileInfo = localManager.getStore(file).fetchInfo(); + return fileInfo.exists() ? fileInfo.getLastModified() : NULL_TIMESTAMP; + } + + /** + * Helper method for #standardDeleteFile. Returns a boolean indicating whether or + * not the delete was successful. + */ + private boolean internalDeleteFile(IFile file, int flags, IProgressMonitor monitor) { + try { + String message = NLS.bind(Messages.resources_deleting, file.getFullPath()); + monitor.beginTask(message, Policy.totalWork); + Policy.checkCanceled(monitor); + + // Do nothing if the file doesn't exist in the workspace. + if (!file.exists()) { + // Indicate that the delete was successful. + return true; + } + // Don't delete contents if this is a linked resource + if (file.isLinked()) { + deletedFile(file); + return true; + } + // If the file doesn't exist on disk then signal to the workspace to delete the + // file and return. + IFileStore fileStore = localManager.getStore(file); + boolean localExists = fileStore.fetchInfo().exists(); + if (!localExists) { + deletedFile(file); + // Indicate that the delete was successful. + return true; + } + + boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0; + boolean force = (flags & IResource.FORCE) != 0; + + // Add the file to the local history if requested by the user. + if (keepHistory) + addToLocalHistory(file); + monitor.worked(Policy.totalWork / 4); + + // We want to fail if force is false and the file is not synchronized with the + // local file system. + if (!force) { + boolean inSync = isSynchronized(file, IResource.DEPTH_ZERO); + // only want to fail if the file still exists. + if (!inSync && localExists) { + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, file.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, file.getFullPath(), message); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + } + monitor.worked(Policy.totalWork / 4); + + // Try to delete the file from the file system. + try { + fileStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork / 4)); + // If the file was successfully deleted from the file system the + // workspace tree should be updated accordingly. + deletedFile(file); + // Indicate that the delete was successful. + return true; + } catch (CoreException e) { + message = NLS.bind(Messages.resources_couldnotDelete, fileStore.toString()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message, e); + failed(status); + } + // Indicate that the delete was unsuccessful. + return false; + } finally { + monitor.done(); + } + } + + /** + * Helper method for #standardDeleteFolder. Returns a boolean indicating + * whether or not the deletion of this folder was successful. Does a best effort + * delete of this resource and its children. + */ + private boolean internalDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) { + String message = NLS.bind(Messages.resources_deleting, folder.getFullPath()); + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + monitor.subTask(message); + Policy.checkCanceled(monitor); + + // Do nothing if the folder doesn't exist in the workspace. + if (!folder.exists()) + return true; + + // Don't delete contents if this is a linked resource + if (folder.isLinked()) { + deletedFolder(folder); + return true; + } + + // If the folder doesn't exist on disk then update the tree and return. + IFileStore fileStore = localManager.getStore(folder); + if (!fileStore.fetchInfo().exists()) { + deletedFolder(folder); + return true; + } + + try { + //this will delete local and workspace + localManager.delete(folder, flags, Policy.subMonitorFor(monitor, Policy.totalWork)); + } catch (CoreException ce) { + message = NLS.bind(Messages.localstore_couldnotDelete, folder.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, folder.getFullPath(), message, ce); + failed(status); + return false; + } + return true; + } + + /** + * Does a best-effort delete on this resource and all its children. + */ + private boolean internalDeleteProject(IProject project, int flags, IProgressMonitor monitor) { + // Recursively delete each member of the project. + IResource[] members = null; + try { + members = project.members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMembers, project.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, project.getFullPath(), message, e); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + boolean deletedChildren = true; + for (int i = 0; i < members.length; i++) { + IResource child = members[i]; + switch (child.getType()) { + case IResource.FILE : + // ignore the .project file for now and delete it last + if (!IProjectDescription.DESCRIPTION_FILE_NAME.equals(child.getName())) + deletedChildren &= internalDeleteFile((IFile) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length)); + break; + case IResource.FOLDER : + deletedChildren &= internalDeleteFolder((IFolder) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length)); + break; + } + } + IFileStore projectStore = localManager.getStore(project); + // Check to see if the children were deleted ok. If there was a problem + // just return as the problem should have been logged by the recursive + // call to the child. + if (!deletedChildren) + // Indicate that the delete was unsuccessful. + return false; + + //Check if there are any undiscovered children of the project on disk other than description file + String[] children; + try { + children = projectStore.childNames(EFS.NONE, null); + } catch (CoreException e) { + //treat failure to access the directory as a non-existent directory + children = new String[0]; + } + boolean force = BitMask.isSet(flags, IResource.FORCE); + if (!force && (children.length != 1 || !IProjectDescription.DESCRIPTION_FILE_NAME.equals(children[0]))) { + String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, project.getName()); + failed(new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, project.getFullPath(), message)); + return false; + } + + //Now delete the project description file + IResource file = project.findMember(IProjectDescription.DESCRIPTION_FILE_NAME); + if (file == null) { + //the .project have may have been recreated on disk automatically by snapshot + IFileStore dotProject = projectStore.getChild(IProjectDescription.DESCRIPTION_FILE_NAME); + try { + dotProject.delete(EFS.NONE, null); + } catch (CoreException e) { + failed(e.getStatus()); + } + } else { + boolean deletedProjectFile = internalDeleteFile((IFile) file, flags, Policy.monitorFor(null)); + if (!deletedProjectFile) { + String message = NLS.bind(Messages.resources_couldnotDelete, file.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + } + + //children are deleted, so now delete the parent + try { + projectStore.delete(EFS.NONE, null); + deletedProject(project); + // Indicate that the delete was successful. + return true; + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_couldnotDelete, projectStore.toString()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message, e); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + } + + /** + * Return true if there is a change in the content area for the project. + */ + private boolean isContentChange(IProject project, IProjectDescription destDescription) { + IProjectDescription srcDescription = ((Project) project).internalGetDescription(); + URI srcLocation = srcDescription.getLocationURI(); + URI destLocation = destDescription.getLocationURI(); + if (srcLocation == null || destLocation == null) + return true; + //don't use URIUtil because we want to treat case rename as a content change + return !srcLocation.equals(destLocation); + } + + /** + * Return true if there is a change in the name of the project. + */ + private boolean isNameChange(IProject project, IProjectDescription description) { + return !project.getName().equals(description.getName()); + } + + /** + * Refreshes the resource hierarchy with its children. In case of failure + * adds an appropriate status to the resource tree's status. + */ + private void safeRefresh(IResource resource) { + try { + resource.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); + } catch (CoreException ce) { + IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, resource.getFullPath(), Messages.refresh_refreshErr, ce); + failed(status); + } + } + + /** + * @see IResourceTree#isSynchronized(IResource, int) + */ + @Override + public boolean isSynchronized(IResource resource, int depth) { + try { + lock.acquire(); + return localManager.isSynchronized(resource, depth); + } finally { + lock.release(); + } + } + + /** + * The specific operation for which this tree was created has completed and this tree + * should not be used anymore. Ensure that this is the case by making it invalid. This + * is checked by all API methods. + */ + void makeInvalid() { + this.isValid = false; + } + + /** + * @see IResourceTree#movedFile(IFile, IFile) + */ + @Override + public void movedFile(IFile source, IFile destination) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!source.exists()) + return; + // If the destination already exists then we have a problem. + if (destination.exists()) { + String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); + // log the status but don't return until we try and move the rest of the resource information. + failed(status); + } + + // Move the resource's persistent properties. + IPropertyManager propertyManager = ((Resource) source).getPropertyManager(); + try { + propertyManager.copy(source, destination, IResource.DEPTH_ZERO); + propertyManager.deleteProperties(source, IResource.DEPTH_ZERO); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource information. + failed(status); + } + + // Move the node in the workspace tree. + Workspace workspace = (Workspace) source.getWorkspace(); + try { + workspace.move((Resource) source, destination.getFullPath(), IResource.DEPTH_ZERO, updateFlags, false); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource information. + failed(status); + } + + // Generate the marker deltas. + try { + workspace.getMarkerManager().moved(source, destination, IResource.DEPTH_ZERO); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + } + + // Copy the local history information + copyLocalHistory(source, destination); + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#movedFolderSubtree(IFolder, IFolder) + */ + @Override + public void movedFolderSubtree(IFolder source, IFolder destination) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the source resource doesn't exist. + if (!source.exists()) + return; + // If the destination already exists then we have an error. + if (destination.exists()) { + String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); + failed(status); + return; + } + + // Move the folder properties. + int depth = IResource.DEPTH_INFINITE; + IPropertyManager propertyManager = ((Resource) source).getPropertyManager(); + try { + propertyManager.copy(source, destination, depth); + propertyManager.deleteProperties(source, depth); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Create the destination node in the tree. + Workspace workspace = (Workspace) source.getWorkspace(); + try { + workspace.move((Resource) source, destination.getFullPath(), depth, updateFlags, false); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Generate the marker deltas. + try { + workspace.getMarkerManager().moved(source, destination, depth); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + } + + // Copy the local history for this folder + copyLocalHistory(source, destination); + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#movedProjectSubtree(IProject, IProjectDescription) + */ + @Override + public boolean movedProjectSubtree(IProject project, IProjectDescription destDescription) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the source resource doesn't exist. + if (!project.exists()) + return true; + + Project source = (Project) project; + Project destination = (Project) source.getWorkspace().getRoot().getProject(destDescription.getName()); + Workspace workspace = (Workspace) source.getWorkspace(); + int depth = IResource.DEPTH_INFINITE; + + // If the name of the source and destination projects are not the same then + // rename the meta area and make changes in the tree. + if (isNameChange(source, destDescription)) { + if (destination.exists()) { + String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); + failed(status); + return false; + } + + // Rename the project metadata area. Close the property store to flush everything to disk + try { + source.getPropertyManager().closePropertyStore(source); + localManager.getHistoryStore().closeHistoryStore(source); + } catch (CoreException e) { + String message = NLS.bind(Messages.properties_couldNotClose, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + final IFileSystem fileSystem = EFS.getLocalFileSystem(); + IFileStore oldMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(source)); + IFileStore newMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(destination)); + try { + oldMetaArea.move(newMetaArea, EFS.NONE, new NullProgressMonitor()); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_moveMeta, oldMetaArea, newMetaArea); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_WRITE_METADATA, destination.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Move the workspace tree. + try { + workspace.move(source, destination.getFullPath(), depth, updateFlags, true); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Clear stale state on the destination project. + ((ProjectInfo) destination.getResourceInfo(false, true)).fixupAfterMove(); + + // Generate marker deltas. + try { + workspace.getMarkerManager().moved(source, destination, depth); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMarkersMove, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + // Copy the local history + copyLocalHistory(source, destination); + } + + // Write the new project description on the destination project. + try { + //moving linked resources may have modified the description in memory + ((ProjectDescription) destDescription).setLinkDescriptions(destination.internalGetDescription().getLinks()); + // moving filters may have modified the description in memory + ((ProjectDescription) destDescription).setFilterDescriptions(destination.internalGetDescription().getFilters()); + // moving variables may have modified the description in memory + ((ProjectDescription) destDescription).setVariableDescriptions(destination.internalGetDescription().getVariables()); + destination.internalSetDescription(destDescription, true); + destination.writeDescription(IResource.FORCE); + } catch (CoreException e) { + String message = Messages.resources_projectDesc; + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e); + failed(status); + } + + // write the private project description, including the project location + try { + workspace.getMetaArea().writePrivateDescription(destination); + } catch (CoreException e) { + failed(e.getStatus()); + } + + // Do a refresh on the destination project to pick up any newly discovered resources + try { + destination.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorRefresh, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e); + failed(status); + return false; + } + return true; + } finally { + lock.release(); + } + } + + /** + * Helper method for moving the project content. Determines the content location + * based on the project description. (default location or user defined?) + */ + private void moveProjectContent(IProject source, IFileStore destStore, int flags, IProgressMonitor monitor) throws CoreException { + try { + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.beginTask(message, 10); + IProjectDescription srcDescription = source.getDescription(); + URI srcLocation = srcDescription.getLocationURI(); + // If the locations are the same (and non-default) then there is nothing to do. + if (srcLocation != null && URIUtil.equals(srcLocation, destStore.toURI())) + return; + + //If this is a replace, just make sure the destination location exists, and return + boolean replace = (flags & IResource.REPLACE) != 0; + if (replace) { + destStore.mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 10)); + return; + } + + // Move the contents on disk. + localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 9)); + + //if this is a deep move, move the contents of any linked resources + if ((flags & IResource.SHALLOW) == 0) { + IResource[] children = source.members(); + for (int i = 0; i < children.length; i++) { + if (children[i].isLinked()) { + message = NLS.bind(Messages.resources_moving, children[i].getFullPath()); + monitor.subTask(message); + IFileStore linkDestination = destStore.getChild(children[i].getName()); + try { + localManager.move(children[i], linkDestination, flags, Policy.monitorFor(null)); + } catch (CoreException ce) { + //log the failure, but keep trying on remaining links + failed(ce.getStatus()); + } + } + } + } + monitor.worked(1); + } finally { + monitor.done(); + } + } + + /** + * @see IResourceTree#standardDeleteFile(IFile, int, IProgressMonitor) + */ + @Override + public void standardDeleteFile(IFile file, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + internalDeleteFile(file, flags, monitor); + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#standardDeleteFolder(IFolder, int, IProgressMonitor) + */ + @Override + public void standardDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + internalDeleteFolder(folder, flags, monitor); + } catch (OperationCanceledException oce) { + safeRefresh(folder); + throw oce; + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardDeleteProject(IProject, int, IProgressMonitor) + */ + @Override + public void standardDeleteProject(IProject project, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_deleting, project.getFullPath()); + monitor.beginTask(message, Policy.totalWork); + // Do nothing if the project doesn't exist in the workspace tree. + if (!project.exists()) + return; + + boolean alwaysDeleteContent = (flags & IResource.ALWAYS_DELETE_PROJECT_CONTENT) != 0; + boolean neverDeleteContent = (flags & IResource.NEVER_DELETE_PROJECT_CONTENT) != 0; + boolean success = true; + + // Delete project content. Don't do anything if the user specified explicitly asked + // not to delete the project content or if the project is closed and + // ALWAYS_DELETE_PROJECT_CONTENT was not specified. + if (alwaysDeleteContent || (project.isOpen() && !neverDeleteContent)) { + // Force is implied if alwaysDeleteContent is true or if the project is in sync + // with the local file system. + if (alwaysDeleteContent || isSynchronized(project, IResource.DEPTH_INFINITE)) { + flags |= IResource.FORCE; + } + + // If the project is open we have to recursively try and delete all the files doing best-effort. + if (project.isOpen()) { + success = internalDeleteProject(project, flags, monitor); + if (!success) { + IFileStore store = localManager.getStore(project); + message = NLS.bind(Messages.resources_couldnotDelete, store.toString()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message); + failed(status); + } + return; + } + + // If the project is closed we can short circuit this operation and delete all the files on disk. + // The .project file is deleted at the end of the operation. + try { + IFileStore projectStore = localManager.getStore(project); + IFileStore members[] = projectStore.childStores(EFS.NONE, null); + for (int i = 0; i < members.length; i++) { + if (!IProjectDescription.DESCRIPTION_FILE_NAME.equals(members[i].getName())) + members[i].delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8 / members.length)); + } + projectStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8 / (members.length > 0 ? members.length : 1))); + } catch (OperationCanceledException oce) { + safeRefresh(project); + throw oce; + } catch (CoreException ce) { + message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message, ce); + failed(status); + return; + } + } + + // Signal that the workspace tree should be updated that the project has been deleted. + if (success) + deletedProject(project); + else { + message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message); + failed(status); + } + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardMoveFile(IFile, IFile, int, IProgressMonitor) + */ + @Override + public void standardMoveFile(IFile source, IFile destination, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.subTask(message); + + // These pre-conditions should all be ok but just in case... + if (!source.exists() || destination.exists() || !destination.getParent().isAccessible()) + throw new IllegalArgumentException(); + + boolean force = (flags & IResource.FORCE) != 0; + boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0; + boolean isDeep = (flags & IResource.SHALLOW) == 0; + + // If the file is not in sync with the local file system and force is false, + // then signal that we have an error. + if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message); + failed(status); + return; + } + monitor.worked(Policy.totalWork / 4); + + // Add the file contents to the local history if requested by the user. + if (keepHistory) + addToLocalHistory(source); + monitor.worked(Policy.totalWork / 4); + + //for shallow move of linked resources, nothing needs to be moved in the file system + if (!isDeep && source.isLinked()) { + movedFile(source, destination); + return; + } + + // If the file was successfully moved in the file system then the workspace + // tree needs to be updated accordingly. Otherwise signal that we have an error. + IFileStore destStore = null; + boolean failedDeletingSource = false; + try { + destStore = localManager.getStore(destination); + //ensure parent of destination exists + destStore.getParent().mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 0)); + localManager.move(source, destStore, flags, monitor); + } catch (CoreException e) { + failed(e.getStatus()); + // did the fail occur after copying to the destination? + failedDeletingSource = destStore != null && destStore.fetchInfo().exists(); + // if so, we should proceed + if (!failedDeletingSource) + return; + } + movedFile(source, destination); + updateMovedFileTimestamp(destination, internalComputeTimestamp(destination)); + if (failedDeletingSource) { + //recreate source file to ensure we are not out of sync + try { + source.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e) { + //ignore secondary failure - we have already logged the main failure + } + } + monitor.worked(Policy.totalWork / 4); + return; + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardMoveFolder(IFolder, IFolder, int, IProgressMonitor) + */ + @Override + public void standardMoveFolder(IFolder source, IFolder destination, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.beginTask(message, 100); + + // These pre-conditions should all be ok but just in case... + if (!source.exists() || destination.exists() || !destination.getParent().isAccessible()) + throw new IllegalArgumentException(); + + // Check to see if we are synchronized with the local file system. If we are in sync then we can + // short circuit this method and do a file system only move. Otherwise we have to recursively + // try and move all resources, doing it in a best-effort manner. + boolean force = (flags & IResource.FORCE) != 0; + if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message); + failed(status); + return; + } + monitor.worked(20); + + //for linked resources, nothing needs to be moved in the file system + boolean isDeep = (flags & IResource.SHALLOW) == 0; + if (!isDeep && (source.isLinked() || source.isVirtual())) { + movedFolderSubtree(source, destination); + return; + } + + // Move the resources in the file system. Only the FORCE flag is valid here so don't + // have to worry about clearing the KEEP_HISTORY flag. + IFileStore destStore = null; + boolean failedDeletingSource = false; + try { + destStore = localManager.getStore(destination); + localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 60)); + } catch (CoreException e) { + failed(e.getStatus()); + // did the fail occur after copying to the destination? + failedDeletingSource = destStore != null && destStore.fetchInfo().exists(); + // if so, we should proceed + if (!failedDeletingSource) + return; + } + movedFolderSubtree(source, destination); + monitor.worked(20); + updateTimestamps(destination, isDeep); + if (failedDeletingSource) { + //the move could have been partially successful, so refresh to ensure we are in sync + try { + source.refreshLocal(IResource.DEPTH_INFINITE, null); + destination.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e) { + //ignore secondary failures -we have already logged main failure + } + } + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardMoveProject(IProject, IProjectDescription, int, IProgressMonitor) + */ + @Override + public void standardMoveProject(IProject source, IProjectDescription description, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.beginTask(message, Policy.totalWork); + + // Double-check this pre-condition. + if (!source.isAccessible()) + throw new IllegalArgumentException(); + + // If there is nothing to do on disk then signal to make the workspace tree + // changes. + if (!isContentChange(source, description)) { + movedProjectSubtree(source, description); + return; + } + + // Check to see if we are synchronized with the local file system. + boolean force = (flags & IResource.FORCE) != 0; + if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { + // FIXME: make this a best effort move? + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message); + failed(status); + return; + } + + IFileStore destinationStore; + try { + destinationStore = computeDestinationStore(description); + //destination can be non-empty on replace + if ((flags & IResource.REPLACE) == 0) + if (!ensureDestinationEmpty(source, destinationStore, monitor)) + return; + } catch (CoreException e) { + //must fail if the destination location cannot be accessd (undefined file system) + message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + return; + } + + // Move the project content in the local file system. + try { + moveProjectContent(source, destinationStore, flags, Policy.subMonitorFor(monitor, Policy.totalWork * 3 / 4)); + } catch (CoreException e) { + message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + //refresh the project because it might have been partially moved + try { + source.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e2) { + //ignore secondary failures + } + } + + // If we got this far the project content has been moved on disk (if necessary) + // and we need to update the workspace tree. + movedProjectSubtree(source, description); + monitor.worked(Policy.totalWork * 1 / 8); + + boolean isDeep = (flags & IResource.SHALLOW) == 0; + updateTimestamps(source.getWorkspace().getRoot().getProject(description.getName()), isDeep); + monitor.worked(Policy.totalWork * 1 / 8); + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#updateMovedFileTimestamp(IFile, long) + */ + @Override + public void updateMovedFileTimestamp(IFile file, long timestamp) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the file doesn't exist in the workspace tree. + if (!file.exists()) + return; + // Update the timestamp in the tree. + ResourceInfo info = ((Resource) file).getResourceInfo(false, true); + // The info should never be null since we just checked that the resource exists in the tree. + localManager.updateLocalSync(info, timestamp); + //remove the linked bit since this resource has been moved in the file system + info.clear(ICoreConstants.M_LINK); + } finally { + lock.release(); + } + } + + /** + * Helper method to update all the timestamps in the tree to match + * those in the file system. Used after a #move. + */ + private void updateTimestamps(IResource root, final boolean isDeep) { + IResourceVisitor visitor = new IResourceVisitor() { + @Override + public boolean visit(IResource resource) { + if (resource.isLinked()) { + if (isDeep && !((Resource) resource).isUnderVirtual()) { + //clear the linked resource bit, if any + ResourceInfo info = ((Resource) resource).getResourceInfo(false, true); + info.clear(ICoreConstants.M_LINK); + } + return true; + } + //only needed if underlying file system does not preserve timestamps + // if (resource.getType() == IResource.FILE) { + // IFile file = (IFile) resource; + // updateMovedFileTimestamp(file, computeTimestamp(file)); + // } + return true; + } + }; + try { + root.accept(visitor, IResource.DEPTH_INFINITE, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + } catch (CoreException e) { + // No exception should be thrown. + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java new file mode 100644 index 0000000000..8def07a9c0 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * 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.runtime.QualifiedName; + +public class RootInfo extends ResourceInfo { + /** The property store for this resource */ + protected Object propertyStore = null; + + /** + * Returns the property store associated with this info. The return value may be null. + */ + @Override + public Object getPropertyStore() { + return propertyStore; + } + + /** + * Override parent's behaviour and do nothing. Sync information + * cannot be stored on the workspace root so we don't need to + * update this counter which is used for deltas. + */ + @Override + public void incrementSyncInfoGenerationCount() { + // do nothing + } + + /** + * Sets the property store associated with this info. The value may be null. + */ + @Override + public void setPropertyStore(Object value) { + propertyStore = value; + } + + /** + * Overrides parent's behaviour since sync information is not + * stored on the workspace root. + */ + @Override + public void setSyncInfo(QualifiedName id, byte[] value) { + // do nothing + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java new file mode 100644 index 0000000000..b28e19237f --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java @@ -0,0 +1,230 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.internal.events.ILifecycleListener; +import org.eclipse.core.internal.events.LifecycleEvent; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.ResourceRuleFactory; +import org.eclipse.core.resources.team.TeamHook; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; + +/** + * Class for calculating scheduling rules for resource changing operations. + * This factory delegates to the TeamHook to obtain an appropriate factory + * for the resource that the operation is proposing to modify. + */ +class Rules implements IResourceRuleFactory, ILifecycleListener { + private final ResourceRuleFactory defaultFactory = new ResourceRuleFactory() {/**/}; + /** + * Map of project names to the factory for that project. + */ + private final Map projectsToRules = Collections.synchronizedMap(new HashMap()); + private final TeamHook teamHook; + private final IWorkspaceRoot root; + + /** + * Creates a new scheduling rule factory for the given workspace + * @param workspace + */ + Rules(Workspace workspace) { + this.root = workspace.getRoot(); + this.teamHook = workspace.getTeamHook(); + workspace.addLifecycleListener(this); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a build operation. + */ + @Override + public ISchedulingRule buildRule() { + //team hook currently cannot change this rule + return root; + } + + /** + * Obtains the scheduling rule from the appropriate factories for a copy operation. + */ + @Override + public ISchedulingRule copyRule(IResource source, IResource destination) { + if (source.getType() == IResource.ROOT || destination.getType() == IResource.ROOT) + return root; + //source is not modified, destination is created + return factoryFor(destination).copyRule(source, destination); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a create operation. + */ + @Override + public ISchedulingRule createRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).createRule(resource); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a delete operation. + */ + @Override + public ISchedulingRule deleteRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).deleteRule(resource); + } + + /** + * Returns the scheduling rule factory for the given resource + */ + private IResourceRuleFactory factoryFor(IResource destination) { + IResourceRuleFactory fac = projectsToRules.get(destination.getFullPath().segment(0)); + if (fac == null) { + //use the default factory if the project is not yet accessible + if (!destination.getProject().isAccessible()) + return defaultFactory; + //ask the team hook to supply one + fac = teamHook.getRuleFactory(destination.getProject()); + projectsToRules.put(destination.getFullPath().segment(0), fac); + } + return fac; + } + + @Override + public void handleEvent(LifecycleEvent event) { + //clear resource rule factory for projects that are about to be closed + //or deleted. It is ok to do this during a PRE event because the rule + //has already been obtained at this point. + switch (event.kind) { + case LifecycleEvent.PRE_PROJECT_CLOSE : + case LifecycleEvent.PRE_PROJECT_DELETE : + case LifecycleEvent.PRE_PROJECT_MOVE : + setRuleFactory((IProject) event.resource, null); + } + } + + /** + * Obtains the scheduling rule from the appropriate factory for a charset change operation. + */ + @Override + public ISchedulingRule charsetRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return null; + return factoryFor(resource).charsetRule(resource); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a derived flag change operation. + */ + @Override + public ISchedulingRule derivedRule(IResource resource) { + //team hook currently cannot change this rule + return null; + } + + /** + * Obtains the scheduling rule from the appropriate factory for a marker change operation. + */ + @Override + public ISchedulingRule markerRule(IResource resource) { + //team hook currently cannot change this rule + return null; + } + + /** + * Obtains the scheduling rule from the appropriate factory for a sync info change operation. + */ + @Override + public ISchedulingRule syncInfoRule(IResource resource) { + //team hook currently cannot change this rule + return null; + } + + /** + * Obtains the scheduling rule from the appropriate factory for a modify operation. + */ + @Override + public ISchedulingRule modifyRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).modifyRule(resource); + } + + /** + * Obtains the scheduling rule from the appropriate factories for a move operation. + */ + @Override + public ISchedulingRule moveRule(IResource source, IResource destination) { + if (source.getType() == IResource.ROOT || destination.getType() == IResource.ROOT) + return root; + //treat a move across projects as a create on the destination and a delete on the source + if (!source.getFullPath().segment(0).equals(destination.getFullPath().segment(0))) + return MultiRule.combine(modifyRule(source.getProject()), modifyRule(destination.getProject())); + return factoryFor(source).moveRule(source, destination); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a refresh operation. + */ + @Override + public ISchedulingRule refreshRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).refreshRule(resource); + } + + /* (non-javadoc) + * Implements TeamHook#setRuleFactory + */ + void setRuleFactory(IProject project, IResourceRuleFactory factory) { + if (factory == null) + projectsToRules.remove(project.getName()); + else + projectsToRules.put(project.getName(), factory); + } + + /** + * Combines rules for each parameter to validateEdit from the corresponding + * rule factories. + */ + @Override + public ISchedulingRule validateEditRule(IResource[] resources) { + if (resources.length == 0) + return null; + //optimize rule for single file + if (resources.length == 1) { + if (resources[0].getType() == IResource.ROOT) + return root; + return factoryFor(resources[0]).validateEditRule(resources); + } + //gather rules for each resource from appropriate factory + HashSet rules = new HashSet<>(); + IResource[] oneResource = new IResource[1]; + for (int i = 0; i < resources.length; i++) { + if (resources[i].getType() == IResource.ROOT) + return root; + oneResource[0] = resources[i]; + ISchedulingRule rule = factoryFor(resources[i]).validateEditRule(oneResource); + if (rule != null) + rules.add(rule); + } + if (rules.isEmpty()) + return null; + if (rules.size() == 1) + return rules.iterator().next(); + ISchedulingRule[] ruleArray = rules.toArray(new ISchedulingRule[rules.size()]); + return new MultiRule(ruleArray); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java new file mode 100644 index 0000000000..35e67306e6 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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.resources; + +import java.io.*; +import java.util.Properties; +import java.util.Set; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; + +/** + * Represents a table of keys and paths used by a plugin to maintain its + * configuration files' names. + */ +public class SafeFileTable { + protected IPath location; + protected Properties table; + + public SafeFileTable(String pluginId) throws CoreException { + location = getWorkspace().getMetaArea().getSafeTableLocationFor(pluginId); + restore(); + } + + public IPath[] getFiles() { + Set set = table.keySet(); + String[] keys = set.toArray(new String[set.size()]); + IPath[] files = new IPath[keys.length]; + for (int i = 0; i < keys.length; i++) + files[i] = new Path(keys[i]); + return files; + } + + protected Workspace getWorkspace() { + return (Workspace) ResourcesPlugin.getWorkspace(); + } + + public IPath lookup(IPath file) { + String result = table.getProperty(file.toOSString()); + return result == null ? null : new Path(result); + } + + public void map(IPath file, IPath aLocation) { + if (aLocation == null) + table.remove(file); + else + table.setProperty(file.toOSString(), aLocation.toOSString()); + } + + public void restore() throws CoreException { + java.io.File target = location.toFile(); + table = new Properties(); + if (!target.exists()) + return; + try { + FileInputStream input = new FileInputStream(target); + try { + table.load(input); + } finally { + input.close(); + } + } catch (IOException e) { + String message = Messages.resources_exSafeRead; + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); + } + } + + public void save() throws CoreException { + java.io.File target = location.toFile(); + try { + FileOutputStream output = new FileOutputStream(target); + try { + table.store(output, "safe table"); //$NON-NLS-1$ + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (IOException e) { + String message = Messages.resources_exSafeSave; + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); + } + } + + public void setLocation(IPath location) { + if (location != null) + this.location = location; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java new file mode 100644 index 0000000000..2185abba04 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; + +public class SaveContext implements ISaveContext { + protected String pluginId; + protected int kind; + protected boolean needDelta; + protected boolean needSaveNumber; + protected SafeFileTable fileTable; + protected int previousSaveNumber; + protected IProject project; + + protected SaveContext(String pluginId, int kind, IProject project) throws CoreException { + this.kind = kind; + this.project = project; + this.pluginId = pluginId; + needDelta = false; + needSaveNumber = false; + fileTable = new SafeFileTable(pluginId); + previousSaveNumber = getWorkspace().getSaveManager().getSaveNumber(pluginId); + } + + public void commit() throws CoreException { + if (needSaveNumber) { + IPath oldLocation = getWorkspace().getMetaArea().getSafeTableLocationFor(pluginId); + getWorkspace().getSaveManager().setSaveNumber(pluginId, getSaveNumber()); + fileTable.setLocation(getWorkspace().getMetaArea().getSafeTableLocationFor(pluginId)); + fileTable.save(); + oldLocation.toFile().delete(); + } + } + + /** + * @see ISaveContext + */ + @Override + public IPath[] getFiles() { + return getFileTable().getFiles(); + } + + protected SafeFileTable getFileTable() { + return fileTable; + } + + /** + * @see ISaveContext + */ + @Override + public int getKind() { + return kind; + } + + public String getPluginId() { + return pluginId; + } + + /** + * @see ISaveContext + */ + @Override + public int getPreviousSaveNumber() { + return previousSaveNumber; + } + + /** + * @see ISaveContext + */ + @Override + public IProject getProject() { + return project; + } + + /** + * @see ISaveContext + */ + @Override + public int getSaveNumber() { + int result = getPreviousSaveNumber() + 1; + return result > 0 ? result : 1; + } + + protected Workspace getWorkspace() { + return (Workspace) ResourcesPlugin.getWorkspace(); + } + + public boolean isDeltaNeeded() { + return needDelta; + } + + /** + * @see ISaveContext + */ + @Override + public IPath lookup(IPath file) { + return getFileTable().lookup(file); + } + + /** + * @see ISaveContext + */ + @Override + public void map(IPath file, IPath location) { + getFileTable().map(file, location); + } + + /** + * @see ISaveContext + */ + @Override + public void needDelta() { + needDelta = true; + } + + /** + * @see ISaveContext + */ + @Override + public void needSaveNumber() { + needSaveNumber = true; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java new file mode 100644 index 0000000000..2e5a00814e --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java @@ -0,0 +1,2139 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Francis Lynch (Wind River) - [301563] Save and load tree snapshots + * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project + * Baltasar Belyavsky (Texas Instruments) - [361675] Order mismatch when saving/restoring workspace trees + * Broadcom Corporation - ongoing development + * Sergey Prigogin (Google) - [437005] Out-of-date .snap file prevents Eclipse from running + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.io.File; +import java.net.URI; +import java.util.*; +import java.util.zip.*; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.internal.events.*; +import org.eclipse.core.internal.localstore.*; +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.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; + +public class SaveManager implements IElementInfoFlattener, IManager, IStringPoolParticipant { + class MasterTable extends Properties { + private static final long serialVersionUID = 1L; + + @Override + public synchronized Object put(Object key, Object value) { + Object prev = super.put(key, value); + if (prev != null && ROOT_SEQUENCE_NUMBER_KEY.equals(key)) { + int prevSeqNum = Integer.parseInt((String) prev); + int currSeqNum = Integer.parseInt((String) value); + if (prevSeqNum > currSeqNum) { + //revert last put operation + super.put(key, prev); + //notify about the problem, do not throw exception but add the exception to know where it occurred + String message = "Cannot set lower sequence number for root (previous: " + prevSeqNum + ", new: " + currSeqNum + "). Ignoring the new value."; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Policy.log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, new IllegalArgumentException(message))); + } + } + return prev; + } + } + + protected static final String ROOT_SEQUENCE_NUMBER_KEY = Path.ROOT.toString() + LocalMetaArea.F_TREE; + protected static final String CLEAR_DELTA_PREFIX = "clearDelta_"; //$NON-NLS-1$ + protected static final String DELTA_EXPIRATION_PREFIX = "deltaExpiration_"; //$NON-NLS-1$ + protected static final int DONE_SAVING = 3; + + /** + * The minimum delay, in milliseconds, between workspace snapshots + */ + private static final long MIN_SNAPSHOT_DELAY = 1000 * 30L; //30 seconds + + /** + * The number of empty operations that are equivalent to a single non- + * trivial operation. + */ + protected static final int NO_OP_THRESHOLD = 20; + + /** constants */ + protected static final int PREPARE_TO_SAVE = 1; + protected static final int ROLLBACK = 4; + protected static final String SAVE_NUMBER_PREFIX = "saveNumber_"; //$NON-NLS-1$ + protected static final int SAVING = 2; + protected ElementTree lastSnap; + protected MasterTable masterTable; + + /** + * A flag indicating that a save operation is occurring. This is a signal + * that snapshot should not be scheduled if a nested operation occurs during + * save. + */ + private volatile boolean isSaving = false; + + /** + * The number of empty (non-changing) operations since the last snapshot. + */ + protected int noopCount = 0; + /** + * The number of non-trivial operations since the last snapshot. + */ + protected int operationCount = 0; + + // Count up the time taken for all saves/snaps on markers and sync info + protected long persistMarkers = 0l; + protected long persistSyncInfo = 0l; + + /** + * In-memory representation of plugins saved state. Maps String (plugin id)-> SavedState. + * This map is accessed from API that is not synchronized, so it requires + * independent synchronization. This is accomplished using a synchronized + * wrapper map. + */ + protected Map savedStates; + + /** + * Ids of plugins that participate on a workspace save. Maps String (plugin id)-> ISaveParticipant. + * This map is accessed from API that is not synchronized, so it requires + * independent synchronization. This is accomplished using a synchronized + * wrapper map. + */ + protected Map saveParticipants; + + protected final DelayedSnapshotJob snapshotJob; + + protected volatile boolean snapshotRequested; + private IStatus snapshotRequestor; + protected Workspace workspace; + //declare debug messages as fields to get sharing + private static final String DEBUG_START = " starting..."; //$NON-NLS-1$ + private static final String DEBUG_FULL_SAVE = "Full save on workspace: "; //$NON-NLS-1$ + private static final String DEBUG_PROJECT_SAVE = "Save on project "; //$NON-NLS-1$ + private static final String DEBUG_SNAPSHOT = "Snapshot: "; //$NON-NLS-1$ + private static final int TREE_BUFFER_SIZE = 1024 * 64;//64KB buffer + + public SaveManager(Workspace workspace) { + this.workspace = workspace; + this.masterTable = new MasterTable(); + this.snapshotJob = new DelayedSnapshotJob(this); + snapshotRequested = false; + snapshotRequestor = null; + saveParticipants = Collections.synchronizedMap(new HashMap(10)); + } + + public ISavedState addParticipant(String pluginId, ISaveParticipant participant) throws CoreException { + // If the plugin was already registered as a save participant we return null + if (saveParticipants.put(pluginId, participant) != null) + return null; + SavedState state = savedStates.get(pluginId); + if (state != null) { + if (isDeltaCleared(pluginId)) { + // this plugin was marked not to receive deltas + state.forgetTrees(); + removeClearDeltaMarks(pluginId); + } else { + try { + // thread safety: (we need to guarantee that the tree is immutable when computing deltas) + // so, the tree inside the saved state needs to be immutable + workspace.prepareOperation(null, null); + workspace.beginOperation(true); + state.newTree = workspace.getElementTree(); + } finally { + workspace.endOperation(null, false, null); + } + return state; + } + } + // if the plug-in has a previous save number, we return a state, otherwise we return null + if (getSaveNumber(pluginId) > 0) + return new SavedState(workspace, pluginId, null, null); + return null; + } + + protected void broadcastLifecycle(final int lifecycle, Map contexts, final MultiStatus warnings, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", contexts.size()); //$NON-NLS-1$ + for (final Iterator> it = contexts.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + String pluginId = entry.getKey(); + final ISaveParticipant participant = saveParticipants.get(pluginId); + //save participants can be removed concurrently + if (participant == null) { + monitor.worked(1); + continue; + } + final SaveContext context = entry.getValue(); + /* Be extra careful when calling lifecycle method on arbitrary plugin */ + ISafeRunnable code = new ISafeRunnable() { + + @Override + public void handleException(Throwable e) { + String message = Messages.resources_saveProblem; + IStatus status = new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, e); + warnings.add(status); + + /* Remove entry for defective plug-in from this save operation */ + it.remove(); + } + + @Override + public void run() throws Exception { + executeLifecycle(lifecycle, participant, context); + } + }; + SafeRunner.run(code); + monitor.worked(1); + } + } finally { + monitor.done(); + } + } + + /** + * Remove the delta expiration timestamp from the master table, either + * because the saved state has been processed, or the delta has expired. + */ + protected void clearDeltaExpiration(String pluginId) { + masterTable.remove(DELTA_EXPIRATION_PREFIX + pluginId); + } + + protected void cleanMasterTable() { + //remove tree file entries for everything except closed projects + for (Iterator it = masterTable.keySet().iterator(); it.hasNext();) { + String key = (String) it.next(); + if (!key.endsWith(LocalMetaArea.F_TREE)) + continue; + String prefix = key.substring(0, key.length() - LocalMetaArea.F_TREE.length()); + //always save the root tree entry + if (prefix.equals(Path.ROOT.toString())) + continue; + IProject project = workspace.getRoot().getProject(prefix); + if (!project.exists() || project.isOpen()) + it.remove(); + } + IPath location = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES); + IPath backup = workspace.getMetaArea().getBackupLocationFor(location); + try { + saveMasterTable(ISaveContext.FULL_SAVE, backup); + } catch (CoreException e) { + Policy.log(e.getStatus()); + backup.toFile().delete(); + return; + } + if (location.toFile().exists() && !location.toFile().delete()) + return; + try { + saveMasterTable(ISaveContext.FULL_SAVE, location); + } catch (CoreException e) { + Policy.log(e.getStatus()); + location.toFile().delete(); + return; + } + backup.toFile().delete(); + } + + /** + * Marks the current participants to not receive deltas next time they are registered + * as save participants. This is done in order to maintain consistency if we crash + * after a snapshot. It would force plug-ins to rebuild their state. + */ + protected void clearSavedDelta() { + synchronized (saveParticipants) { + for (Iterator i = saveParticipants.keySet().iterator(); i.hasNext();) { + String pluginId = i.next(); + masterTable.setProperty(CLEAR_DELTA_PREFIX + pluginId, "true"); //$NON-NLS-1$ + } + } + } + + /** + * Collects the set of ElementTrees we are still interested in, + * and removes references to any other trees. + */ + protected void collapseTrees(Map contexts) throws CoreException { + //collect trees we're interested in + + //forget saved trees, if they are not used by registered participants + synchronized (savedStates) { + for (Iterator i = contexts.values().iterator(); i.hasNext();) { + SaveContext context = i.next(); + forgetSavedTree(context.getPluginId()); + } + } + + //trees for plugin saved states + ArrayList trees = new ArrayList<>(); + synchronized (savedStates) { + for (Iterator i = savedStates.values().iterator(); i.hasNext();) { + SavedState state = i.next(); + if (state.oldTree != null) { + trees.add(state.oldTree); + } + } + } + + //trees for builders + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) { + IProject project = projects[i]; + if (project.isOpen()) { + ArrayList builderInfos = workspace.getBuildManager().createBuildersPersistentInfo(project); + if (builderInfos != null) { + for (Iterator it = builderInfos.iterator(); it.hasNext();) { + BuilderPersistentInfo info = it.next(); + trees.add(info.getLastBuiltTree()); + } + } + } + } + + //no need to collapse if there are no trees at this point + if (trees.isEmpty()) + return; + + //the complete tree + trees.add(workspace.getElementTree()); + + //collapse the trees + //sort trees in topological order, and set the parent of each + //tree to its parent in the topological ordering. + ElementTree[] treeArray = new ElementTree[trees.size()]; + trees.toArray(treeArray); + ElementTree[] sorted = sortTrees(treeArray); + // if there was a problem sorting the tree, bail on trying to collapse. + // We will be able to GC the layers at a later time. + if (sorted == null) + return; + for (int i = 1; i < sorted.length; i++) + sorted[i].collapseTo(sorted[i - 1]); + } + + protected void commit(Map contexts) throws CoreException { + for (Iterator i = contexts.values().iterator(); i.hasNext();) + i.next().commit(); + } + + /** + * Given a collection of save participants, compute the collection of + * SaveContexts to use during the save lifecycle. + * The keys are plugins and values are SaveContext objects. + */ + protected Map computeSaveContexts(String[] pluginIds, int kind, IProject project) { + HashMap result = new HashMap<>(pluginIds.length); + for (int i = 0; i < pluginIds.length; i++) { + String pluginId = pluginIds[i]; + try { + SaveContext context = new SaveContext(pluginId, kind, project); + result.put(pluginId, context); + } catch (CoreException e) { + // FIXME: should return a status to the user and not just log it + Policy.log(e.getStatus()); + } + } + return result; + } + + /** + * Returns a table mapping having the plug-in id as the key and the old tree + * as the value. + * This table is based on the union of the current savedStates + * and the given table of contexts. The specified tree is used as the tree for + * any newly created saved states. This method is used to compute the set of + * saved states to be written out. + */ + protected Map computeStatesToSave(Map contexts, ElementTree current) { + HashMap result = new HashMap<>(savedStates.size() * 2); + synchronized (savedStates) { + for (Iterator i = savedStates.values().iterator(); i.hasNext();) { + SavedState state = i.next(); + if (state.oldTree != null) + result.put(state.pluginId, state.oldTree); + } + } + for (Iterator i = contexts.values().iterator(); i.hasNext();) { + SaveContext context = i.next(); + if (!context.isDeltaNeeded()) + continue; + String pluginId = context.getPluginId(); + result.put(pluginId, current); + } + return result; + } + + protected void executeLifecycle(int lifecycle, ISaveParticipant participant, SaveContext context) throws CoreException { + switch (lifecycle) { + case PREPARE_TO_SAVE : + participant.prepareToSave(context); + break; + case SAVING : + try { + if (ResourceStats.TRACE_SAVE_PARTICIPANTS) + ResourceStats.startSave(participant); + participant.saving(context); + } finally { + if (ResourceStats.TRACE_SAVE_PARTICIPANTS) + ResourceStats.endSave(); + } + break; + case DONE_SAVING : + participant.doneSaving(context); + break; + case ROLLBACK : + participant.rollback(context); + break; + default : + Assert.isTrue(false, "Invalid save lifecycle code"); //$NON-NLS-1$ + } + } + + public void forgetSavedTree(String pluginId) { + if (pluginId == null) { + synchronized (savedStates) { + for (Iterator i = savedStates.values().iterator(); i.hasNext();) + i.next().forgetTrees(); + } + } else { + SavedState state = savedStates.get(pluginId); + if (state != null) + state.forgetTrees(); + } + } + + /** + * Used in the policy for cleaning up tree's of plug-ins that are not often activated. + */ + protected long getDeltaExpiration(String pluginId) { + String result = masterTable.getProperty(DELTA_EXPIRATION_PREFIX + pluginId); + return (result == null) ? System.currentTimeMillis() : Long.parseLong(result); + } + + protected Properties getMasterTable() { + return masterTable; + } + + public int getSaveNumber(String pluginId) { + String value = masterTable.getProperty(SAVE_NUMBER_PREFIX + pluginId); + return (value == null) ? 0 : Integer.parseInt(value); + } + + protected String[] getSaveParticipantPluginIds() { + synchronized (saveParticipants) { + return saveParticipants.keySet().toArray(new String[saveParticipants.size()]); + } + } + + /** + * Hooks the end of a save operation, for debugging and performance + * monitoring purposes. + */ + private void hookEndSave(int kind, IProject project, long start) { + if (ResourceStats.TRACE_SNAPSHOT && kind == ISaveContext.SNAPSHOT) + ResourceStats.endSnapshot(); + if (Policy.DEBUG_SAVE) { + String endMessage = null; + switch (kind) { + case ISaveContext.FULL_SAVE : + endMessage = DEBUG_FULL_SAVE; + break; + case ISaveContext.SNAPSHOT : + endMessage = DEBUG_SNAPSHOT; + break; + case ISaveContext.PROJECT_SAVE : + endMessage = DEBUG_PROJECT_SAVE + project.getFullPath() + ": "; //$NON-NLS-1$ + break; + } + if (endMessage != null) + Policy.debug(endMessage + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ + } + } + + /** + * Hooks the start of a save operation, for debugging and performance + * monitoring purposes. + */ + private void hookStartSave(int kind, Project project) { + if (ResourceStats.TRACE_SNAPSHOT && kind == ISaveContext.SNAPSHOT) + ResourceStats.startSnapshot(); + if (Policy.DEBUG_SAVE) { + switch (kind) { + case ISaveContext.FULL_SAVE : + Policy.debug(DEBUG_FULL_SAVE + DEBUG_START); + break; + case ISaveContext.SNAPSHOT : + Policy.debug(DEBUG_SNAPSHOT + DEBUG_START); + break; + case ISaveContext.PROJECT_SAVE : + Policy.debug(DEBUG_PROJECT_SAVE + project.getFullPath() + DEBUG_START); + break; + } + } + } + + /** + * Initializes the snapshot mechanism for this workspace. + */ + protected void initSnap(IProgressMonitor monitor) { + // Discard any pending snapshot request. + snapshotJob.cancel(); + // The "lastSnap" tree must be frozen as the exact tree obtained from startup, + // otherwise ensuing snapshot deltas may be based on an incorrect tree (see bug 12575). + lastSnap = workspace.getElementTree(); + lastSnap.immutable(); + workspace.newWorkingTree(); + operationCount = 0; + // Delete the snapshot files, if any. + IPath location = workspace.getMetaArea().getSnapshotLocationFor(workspace.getRoot()); + java.io.File target = location.toFile().getParentFile(); + FilenameFilter filter = new FilenameFilter() { + @Override + public boolean accept(java.io.File dir, String name) { + if (!name.endsWith(LocalMetaArea.F_SNAP)) + return false; + for (int i = 0; i < name.length() - LocalMetaArea.F_SNAP.length(); i++) { + char c = name.charAt(i); + if (c < '0' || c > '9') + return false; + } + return true; + } + }; + String[] candidates = target.list(filter); + if (candidates != null) + removeFiles(target, candidates, Collections. emptyList()); + } + + protected boolean isDeltaCleared(String pluginId) { + String clearDelta = masterTable.getProperty(CLEAR_DELTA_PREFIX + pluginId); + return clearDelta != null && clearDelta.equals("true"); //$NON-NLS-1$ + } + + protected boolean isOldPluginTree(String pluginId) { + // first, check if this plug-ins was marked not to receive a delta + if (isDeltaCleared(pluginId)) + return false; + //see if the plugin is still installed + if (Platform.getBundle(pluginId) == null) + return true; + + //finally see if the delta has past its expiry date + long deltaAge = System.currentTimeMillis() - getDeltaExpiration(pluginId); + return deltaAge > workspace.internalGetDescription().getDeltaExpiration(); + } + + /** + * @see IElementInfoFlattener#readElement(IPath, DataInput) + */ + @Override + public Object readElement(IPath path, DataInput input) throws IOException { + Assert.isNotNull(path); + Assert.isNotNull(input); + // read the flags and pull out the type. + int flags = input.readInt(); + int type = (flags & ICoreConstants.M_TYPE) >> ICoreConstants.M_TYPE_START; + ResourceInfo info = workspace.newElement(type); + info.readFrom(flags, input); + return info; + } + + private void rememberSnapshotRequestor() { + if (Policy.DEBUG_SAVE) + Policy.debug(new RuntimeException("Scheduling workspace snapshot")); //$NON-NLS-1$ + if (snapshotRequestor == null) { + String msg = "The workspace will exit with unsaved changes in this session."; //$NON-NLS-1$ + snapshotRequestor = new ResourceStatus(ICoreConstants.CRASH_DETECTED, msg); + } + } + + /** + * Remove marks from current save participants. This marks prevent them to receive their + * deltas when they register themselves as save participants. + */ + protected void removeClearDeltaMarks() { + synchronized (saveParticipants) { + for (Iterator i = saveParticipants.keySet().iterator(); i.hasNext();) { + String pluginId = i.next(); + removeClearDeltaMarks(pluginId); + } + } + } + + protected void removeClearDeltaMarks(String pluginId) { + masterTable.setProperty(CLEAR_DELTA_PREFIX + pluginId, "false"); //$NON-NLS-1$ + } + + protected void removeFiles(java.io.File root, String[] candidates, List exclude) { + for (int i = 0; i < candidates.length; i++) { + boolean delete = true; + for (ListIterator it = exclude.listIterator(); it.hasNext();) { + String s = it.next(); + if (s.equals(candidates[i])) { + it.remove(); + delete = false; + break; + } + } + if (delete) + new java.io.File(root, candidates[i]).delete(); + } + } + + private void removeGarbage(DataOutputStream output, IPath location, IPath tempLocation) throws IOException { + if (output.size() == 0) { + output.close(); + location.toFile().delete(); + tempLocation.toFile().delete(); + } + } + + public void removeParticipant(String pluginId) { + saveParticipants.remove(pluginId); + } + + protected void removeUnusedSafeTables() { + List valuables = new ArrayList<>(10); + IPath location = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES); + valuables.add(location.lastSegment()); // add master table + for (Enumeration e = masterTable.keys(); e.hasMoreElements();) { + String key = (String) e.nextElement(); + if (key.startsWith(SAVE_NUMBER_PREFIX)) { + String pluginId = key.substring(SAVE_NUMBER_PREFIX.length()); + valuables.add(workspace.getMetaArea().getSafeTableLocationFor(pluginId).lastSegment()); + } + } + java.io.File target = location.toFile().getParentFile(); + String[] candidates = target.list(); + if (candidates == null) + return; + removeFiles(target, candidates, valuables); + } + + protected void removeUnusedTreeFiles() { + // root resource + List valuables = new ArrayList<>(10); + IPath location = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), false); + valuables.add(location.lastSegment()); + java.io.File target = location.toFile().getParentFile(); + FilenameFilter filter = new FilenameFilter() { + @Override + public boolean accept(java.io.File dir, String name) { + return name.endsWith(LocalMetaArea.F_TREE); + } + }; + String[] candidates = target.list(filter); + if (candidates != null) + removeFiles(target, candidates, valuables); + + // projects + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) { + location = workspace.getMetaArea().getTreeLocationFor(projects[i], false); + valuables.add(location.lastSegment()); + target = location.toFile().getParentFile(); + candidates = target.list(filter); + if (candidates != null) + removeFiles(target, candidates, valuables); + } + } + + protected void reportSnapshotRequestor() { + if (snapshotRequestor != null) + Policy.log(snapshotRequestor); + } + + public void requestSnapshot() { + snapshotRequested = true; + } + + /** + * Reset the snapshot mechanism for the non-workspace files. This + * includes the markers and sync info. + */ + protected void resetSnapshots(IResource resource) throws CoreException { + Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT); + String message; + + // delete the snapshot file, if any + java.io.File file = workspace.getMetaArea().getMarkersSnapshotLocationFor(resource).toFile(); + if (file.exists()) + file.delete(); + if (file.exists()) { + message = Messages.resources_resetMarkers; + throw new ResourceException(IResourceStatus.FAILED_DELETE_METADATA, resource.getFullPath(), message, null); + } + + // delete the snapshot file, if any + file = workspace.getMetaArea().getSyncInfoSnapshotLocationFor(resource).toFile(); + if (file.exists()) + file.delete(); + if (file.exists()) { + message = Messages.resources_resetSync; + throw new ResourceException(IResourceStatus.FAILED_DELETE_METADATA, resource.getFullPath(), message, null); + } + + // if we have the workspace root then recursive over the projects. + // only do open projects since closed ones are saved elsewhere + if (resource.getType() == IResource.PROJECT) + return; + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + resetSnapshots(projects[i]); + } + + /** + * Restores the state of this workspace by opening the projects + * which were open when it was last saved. + */ + protected void restore(IProgressMonitor monitor) throws CoreException { + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore workspace: starting..."); //$NON-NLS-1$ + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", 50); //$NON-NLS-1$ + // need to open the tree to restore, but since we're not + // inside an operation, be sure to close it afterwards + workspace.newWorkingTree(); + try { + String msg = Messages.resources_startupProblems; + MultiStatus problems = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, msg, null); + + restoreMasterTable(); + // restore the saved tree and overlay the snapshots if any + restoreTree(Policy.subMonitorFor(monitor, 10)); + restoreSnapshots(Policy.subMonitorFor(monitor, 10)); + + // tolerate failure for non-critical information + // if startup fails, the entire workspace is shot + try { + restoreMarkers(workspace.getRoot(), false, Policy.subMonitorFor(monitor, 10)); + } catch (CoreException e) { + problems.merge(e.getStatus()); + } + try { + restoreSyncInfo(workspace.getRoot(), Policy.subMonitorFor(monitor, 10)); + } catch (CoreException e) { + problems.merge(e.getStatus()); + } + // restore meta info last because it might close a project if its description is not readable + restoreMetaInfo(problems, Policy.subMonitorFor(monitor, 10)); + IProject[] roots = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < roots.length; i++) + ((Project) roots[i]).startup(); + if (!problems.isOK()) + Policy.log(problems); + } finally { + workspace.getElementTree().immutable(); + } + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Restores the contents of this project. Throw + * an exception if the project could not be restored. + * @return true if the project data was restored successfully, + * and false if non-critical problems occurred while restoring. + * @exception CoreException if the project could not be restored. + */ + protected boolean restore(Project project, IProgressMonitor monitor) throws CoreException { + boolean status = true; + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": starting..."); //$NON-NLS-1$ //$NON-NLS-2$ + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", 40); //$NON-NLS-1$ + if (project.isOpen()) { + status = restoreTree(project, Policy.subMonitorFor(monitor, 10)); + } else { + monitor.worked(10); + } + restoreMarkers(project, true, Policy.subMonitorFor(monitor, 10)); + restoreSyncInfo(project, Policy.subMonitorFor(monitor, 10)); + // restore meta info last because it might close a project if its description is not found + restoreMetaInfo(project, Policy.subMonitorFor(monitor, 10)); + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return status; + } + + /** + * Restores the contents of this project from a refresh snapshot, if possible. + * Throws an exception if the snapshot is found but an error occurs when reading + * the file. + * @return true if the project data was restored successfully, + * and false if the refresh snapshot was not found or could not be opened. + * @exception CoreException if an error occurred reading the snapshot file. + */ + protected boolean restoreFromRefreshSnapshot(Project project, IProgressMonitor monitor) throws CoreException { + boolean status = true; + IPath snapshotPath = workspace.getMetaArea().getRefreshLocationFor(project); + java.io.File snapshotFile = snapshotPath.toFile(); + if (!snapshotFile.exists()) + return false; + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": starting..."); //$NON-NLS-1$ //$NON-NLS-2$ + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", 40); //$NON-NLS-1$ + status = restoreTreeFromRefreshSnapshot(project, snapshotFile, Policy.subMonitorFor(monitor, 40)); + if (status) { + // load the project description and set internal description + ProjectDescription description = workspace.getFileSystemManager().read(project, true); + project.internalSetDescription(description, false); + workspace.getMetaArea().clearRefresh(project); + } + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return status; + } + + /** + * Reads the markers which were originally saved + * for the tree rooted by the given resource. + */ + protected void restoreMarkers(IResource resource, boolean generateDeltas, IProgressMonitor monitor) throws CoreException { + Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT); + long start = System.currentTimeMillis(); + MarkerManager markerManager = workspace.getMarkerManager(); + // when restoring a project, only load markers if it is open + if (resource.isAccessible()) + markerManager.restore(resource, generateDeltas, monitor); + + // if we have the workspace root then restore markers for its projects + if (resource.getType() == IResource.PROJECT) { + if (Policy.DEBUG_RESTORE_MARKERS) { + Policy.debug("Restore Markers for " + resource.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return; + } + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + if (projects[i].isAccessible()) + markerManager.restore(projects[i], generateDeltas, monitor); + if (Policy.DEBUG_RESTORE_MARKERS) { + Policy.debug("Restore Markers for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + protected void restoreMasterTable() throws CoreException { + long start = System.currentTimeMillis(); + masterTable.clear(); + IPath location = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES); + java.io.File target = location.toFile(); + if (!target.exists()) { + location = workspace.getMetaArea().getBackupLocationFor(location); + target = location.toFile(); + if (!target.exists()) + return; + } + try { + SafeChunkyInputStream input = new SafeChunkyInputStream(target); + try { + masterTable.load(input); + } finally { + input.close(); + } + } catch (IOException e) { + String message = Messages.resources_exMasterTable; + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); + } + if (Policy.DEBUG_RESTORE_MASTERTABLE) + Policy.debug("Restore master table for " + location + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Restores the state of this workspace by opening the projects + * which were open when it was last saved. + */ + protected void restoreMetaInfo(MultiStatus problems, IProgressMonitor monitor) { + if (Policy.DEBUG_RESTORE_METAINFO) + Policy.debug("Restore workspace metainfo: starting..."); //$NON-NLS-1$ + long start = System.currentTimeMillis(); + IProject[] roots = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < roots.length; i++) { + //fatal to throw exceptions during startup + try { + restoreMetaInfo((Project) roots[i], monitor); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_readMeta, roots[i].getName()); + problems.merge(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, roots[i].getFullPath(), message, e)); + } + } + if (Policy.DEBUG_RESTORE_METAINFO) + Policy.debug("Restore workspace metainfo: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Restores the contents of this project. Throw an exception if the + * project description could not be restored. + */ + protected void restoreMetaInfo(Project project, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + ProjectDescription description = null; + CoreException failure = null; + try { + if (project.isOpen()) + description = workspace.getFileSystemManager().read(project, true); + else + //for closed projects, just try to read the legacy .prj file, + //because the project location is stored there. + description = workspace.getMetaArea().readOldDescription(project); + } catch (CoreException e) { + failure = e; + } + // If we had an open project and there was an error reading the description + // from disk, close the project and give it a default description. If the project + // was already closed then just set a default description. + if (description == null) { + description = new ProjectDescription(); + description.setName(project.getName()); + //try to read private metadata and add to the description + workspace.getMetaArea().readPrivateDescription(project, description); + } + project.internalSetDescription(description, false); + if (failure != null) { + // write the project tree ... + writeTree(project, IResource.DEPTH_INFINITE); + // ... and close the project + project.internalClose(monitor); + throw failure; + } + if (Policy.DEBUG_RESTORE_METAINFO) + Policy.debug("Restore metainfo for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Restores the workspace tree from snapshot files in the event + * of a crash. The workspace tree must be open when this method + * is called, and will be open at the end of this method. In the + * event of a crash recovery, the snapshot file is not deleted until + * the next successful save. + */ + protected void restoreSnapshots(IProgressMonitor monitor) { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + IPath snapLocation = workspace.getMetaArea().getSnapshotLocationFor(workspace.getRoot()); + java.io.File localFile = snapLocation.toFile(); + + if (!localFile.exists()) { + // The snapshot corresponding to the current tree version doesn't exist. + // Try the legacy non-versioned snapshot, but ignore it if it is older than + // the tree. + snapLocation = workspace.getMetaArea().getLegacySnapshotLocationFor(workspace.getRoot()); + localFile = snapLocation.toFile(); + if (!localFile.exists() || isSnapshotOlderThanTree(localFile)) { + // If the snapshot file doesn't exist, there was no crash. + // Just initialize the snapshot file and return. + initSnap(Policy.subMonitorFor(monitor, Policy.totalWork / 2)); + return; + } + } + // If we have a snapshot file, the workspace was shutdown without being saved or crashed. + workspace.setCrashed(true); + try { + /* Read each of the snapshots and lay them on top of the current tree.*/ + ElementTree complete = workspace.getElementTree(); + complete.immutable(); + DataInputStream input = new DataInputStream(new SafeChunkyInputStream(localFile)); + try { + WorkspaceTreeReader reader = WorkspaceTreeReader.getReader(workspace, input.readInt()); + complete = reader.readSnapshotTree(input, complete, monitor); + } finally { + FileUtil.safeClose(input); + //reader returned an immutable tree, but since we're inside + //an operation, we must return an open tree + lastSnap = complete; + complete = complete.newEmptyDelta(); + workspace.tree = complete; + } + } catch (Exception e) { + // only log the exception, we should not fail restoring the snapshot + message = Messages.resources_snapRead; + Policy.log(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, null, message, e)); + } + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE_SNAPSHOTS) + Policy.debug("Restore snapshots for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Checks if the given snapshot file is older than the tree file. + * + * @param snapshot the snapshot file to check + * @return {@code true} if the snapshot file is older than the tree file or the tree file + * does not exist + */ + private boolean isSnapshotOlderThanTree(File snapshot) { + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), false); + File tree = treeLocation.toFile(); + if (!tree.exists()) { + treeLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + tree = treeLocation.toFile(); + if (!tree.exists()) + return false; + } + return snapshot.lastModified() < tree.lastModified(); + } + + /** + * Reads the sync info which was originally saved + * for the tree rooted by the given resource. + */ + protected void restoreSyncInfo(IResource resource, IProgressMonitor monitor) throws CoreException { + Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT); + long start = System.currentTimeMillis(); + Synchronizer synchronizer = (Synchronizer) workspace.getSynchronizer(); + // when restoring a project, only load sync info if it is open + if (resource.isAccessible()) + synchronizer.restore(resource, monitor); + + // restore sync info for all projects if we were given the workspace root. + if (resource.getType() == IResource.PROJECT) { + if (Policy.DEBUG_RESTORE_SYNCINFO) { + Policy.debug("Restore SyncInfo for " + resource.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return; + } + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + if (projects[i].isAccessible()) + synchronizer.restore(projects[i], monitor); + if (Policy.DEBUG_RESTORE_SYNCINFO) { + Policy.debug("Restore SyncInfo for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Reads the contents of the tree rooted by the given resource from the + * file system. This method is used when restoring a complete workspace + * after workspace save/shutdown. + * @exception CoreException if the workspace could not be restored. + */ + protected void restoreTree(IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), false); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + if (!treeLocation.toFile().exists() && !tempLocation.toFile().exists()) { + savedStates = Collections.synchronizedMap(new HashMap(10)); + return; + } + try { + DataInputStream input = new DataInputStream(new SafeFileInputStream(treeLocation.toOSString(), tempLocation.toOSString(), TREE_BUFFER_SIZE)); + try { + WorkspaceTreeReader.getReader(workspace, input.readInt()).readTree(input, monitor); + } finally { + input.close(); + } + } catch (IOException e) { + String msg = NLS.bind(Messages.resources_readMeta, treeLocation.toOSString()); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, treeLocation, msg, e); + } + if (Policy.DEBUG_RESTORE_TREE) { + Policy.debug("Restore Tree for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Restores the trees for the builders of this project from the local disk. + * Does nothing if the tree file does not exist (this means the + * project has never been saved). This method is + * used when restoring a saved/closed project. restoreTree(Workspace) is + * used when restoring a complete workspace after workspace save/shutdown. + * @return true if the tree file exists, false otherwise. + * @exception CoreException if the project could not be restored. + */ + protected boolean restoreTree(Project project, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(project, false); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + if (!treeLocation.toFile().exists() && !tempLocation.toFile().exists()) + return false; + DataInputStream input = new DataInputStream(new SafeFileInputStream(treeLocation.toOSString(), tempLocation.toOSString())); + try { + WorkspaceTreeReader reader = WorkspaceTreeReader.getReader(workspace, input.readInt()); + reader.readTree(project, input, Policy.subMonitorFor(monitor, Policy.totalWork)); + } finally { + input.close(); + } + } catch (IOException e) { + message = NLS.bind(Messages.resources_readMeta, project.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, project.getFullPath(), message, e); + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE_TREE) { + Policy.debug("Restore Tree for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return true; + } + + /** + * Restores a tree saved as a refresh snapshot to a specified URI. + * @return true if the snapshot exists, false otherwise. + * @exception CoreException if the project could not be restored. + */ + protected boolean restoreTreeFromRefreshSnapshot(Project project, java.io.File snapshotFile, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + IPath snapshotPath = null; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + InputStream snapIn = new FileInputStream(snapshotFile); + ZipInputStream zip = new ZipInputStream(snapIn); + ZipEntry treeEntry = zip.getNextEntry(); + if (treeEntry == null || !treeEntry.getName().equals("resource-index.tree")) { //$NON-NLS-1$ + zip.close(); + return false; + } + DataInputStream input = new DataInputStream(zip); + try { + WorkspaceTreeReader reader = WorkspaceTreeReader.getReader(workspace, input.readInt(), true); + reader.readTree(project, input, Policy.subMonitorFor(monitor, Policy.totalWork)); + } finally { + input.close(); + zip.close(); + } + } catch (IOException e) { + snapshotPath = new Path(snapshotFile.getPath()); + message = NLS.bind(Messages.resources_readMeta, snapshotPath); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, snapshotPath, message, e); + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE_TREE) { + Policy.debug("Restore Tree for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return true; + } + + class InternalMonitorWrapper extends ProgressMonitorWrapper { + private boolean ignoreCancel; + + public InternalMonitorWrapper(IProgressMonitor monitor) { + super(Policy.monitorFor(monitor)); + } + + public void ignoreCancelState(boolean ignore) { + this.ignoreCancel = ignore; + } + + @Override + public boolean isCanceled() { + return ignoreCancel ? false : super.isCanceled(); + } + } + + public IStatus save(int kind, Project project, IProgressMonitor monitor) throws CoreException { + return save(kind, false, project, monitor); + } + + public IStatus save(int kind, boolean keepConsistencyWhenCanceled, Project project, IProgressMonitor parentMonitor) throws CoreException { + InternalMonitorWrapper monitor = new InternalMonitorWrapper(parentMonitor); + monitor.ignoreCancelState(keepConsistencyWhenCanceled); + try { + isSaving = true; + String message = Messages.resources_saving_0; + monitor.beginTask(message, 7); + message = Messages.resources_saveWarnings; + MultiStatus warnings = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.WARNING, message, null); + ISchedulingRule rule = project != null ? (IResource) project : workspace.getRoot(); + try { + workspace.prepareOperation(rule, monitor); + workspace.beginOperation(false); + hookStartSave(kind, project); + long start = System.currentTimeMillis(); + Map contexts = computeSaveContexts(getSaveParticipantPluginIds(), kind, project); + broadcastLifecycle(PREPARE_TO_SAVE, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + try { + broadcastLifecycle(SAVING, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + switch (kind) { + case ISaveContext.FULL_SAVE : + // save the complete tree and remember all of the required saved states + saveTree(contexts, Policy.subMonitorFor(monitor, 1)); + // reset the snapshot state. + initSnap(null); + snapshotRequestor = null; + //save master table right after saving tree to ensure correct tree number is saved + cleanMasterTable(); + // save all of the markers and all sync info in the workspace + persistMarkers = 0l; + persistSyncInfo = 0l; + visitAndSave(workspace.getRoot()); + monitor.worked(1); + if (Policy.DEBUG_SAVE) { + Policy.debug("Total Save Markers: " + persistMarkers + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + Policy.debug("Total Save Sync Info: " + persistSyncInfo + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + // reset the snap shot files + resetSnapshots(workspace.getRoot()); + //remove unused files + removeUnusedSafeTables(); + removeUnusedTreeFiles(); + + // history pruning can be always canceled + monitor.ignoreCancelState(false); + workspace.getFileSystemManager().getHistoryStore().clean(Policy.subMonitorFor(monitor, 1)); + monitor.ignoreCancelState(keepConsistencyWhenCanceled); + + // write out all metainfo (e.g., workspace/project descriptions) + saveMetaInfo(warnings, Policy.subMonitorFor(monitor, 1)); + break; + case ISaveContext.SNAPSHOT : + snapTree(workspace.getElementTree(), Policy.subMonitorFor(monitor, 1)); + // snapshot the markers and sync info for the workspace + persistMarkers = 0l; + persistSyncInfo = 0l; + visitAndSnap(workspace.getRoot()); + monitor.worked(1); + if (Policy.DEBUG_SAVE) { + Policy.debug("Total Snap Markers: " + persistMarkers + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + Policy.debug("Total Snap Sync Info: " + persistSyncInfo + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + collapseTrees(contexts); + clearSavedDelta(); + // write out all metainfo (e.g., workspace/project descriptions) + saveMetaInfo(warnings, Policy.subMonitorFor(monitor, 1)); + break; + case ISaveContext.PROJECT_SAVE : + writeTree(project, IResource.DEPTH_INFINITE); + monitor.worked(1); + // save markers and sync info + visitAndSave(project); + monitor.worked(1); + // reset the snapshot file + resetSnapshots(project); + IStatus result = saveMetaInfo(project, null); + if (!result.isOK()) + warnings.merge(result); + monitor.worked(1); + break; + } + // save contexts + commit(contexts); + if (kind == ISaveContext.FULL_SAVE) + removeClearDeltaMarks(); + //this must be done after committing save contexts to update participant save numbers + saveMasterTable(kind); + broadcastLifecycle(DONE_SAVING, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + hookEndSave(kind, project, start); + return warnings; + } catch (CoreException e) { + broadcastLifecycle(ROLLBACK, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + // rollback ResourcesPlugin master table + restoreMasterTable(); + throw e; // re-throw + } + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, false, Policy.monitorFor(null)); + } + } finally { + isSaving = false; + monitor.done(); + } + } + + protected void saveMasterTable(int kind) throws CoreException { + saveMasterTable(kind, workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES)); + } + + protected void saveMasterTable(int kind, IPath location) throws CoreException { + long start = System.currentTimeMillis(); + java.io.File target = location.toFile(); + try { + if (kind == ISaveContext.FULL_SAVE || kind == ISaveContext.SNAPSHOT) + validateMasterTableBeforeSave(target); + SafeChunkyOutputStream output = new SafeChunkyOutputStream(target); + try { + masterTable.store(output, "master table"); //$NON-NLS-1$ + output.succeed(); + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (IOException e) { + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, NLS.bind(Messages.resources_exSaveMaster, location.toOSString()), e); + } + if (Policy.DEBUG_SAVE_MASTERTABLE) + Policy.debug("Save master table for " + location + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Writes the metainfo (e.g. descriptions) of the given workspace and + * all projects to the local disk. + */ + protected void saveMetaInfo(MultiStatus problems, IProgressMonitor monitor) throws CoreException { + if (Policy.DEBUG_SAVE_METAINFO) + Policy.debug("Save workspace metainfo: starting..."); //$NON-NLS-1$ + long start = System.currentTimeMillis(); + // save preferences (workspace description, path variables, etc) + ResourcesPlugin.getPlugin().savePluginPreferences(); + // save projects' meta info + IProject[] roots = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < roots.length; i++) + if (roots[i].isAccessible()) { + IStatus result = saveMetaInfo((Project) roots[i], null); + if (!result.isOK()) + problems.merge(result); + } + if (Policy.DEBUG_SAVE_METAINFO) + Policy.debug("Save workspace metainfo: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Ensures that the project meta-info is saved. The project meta-info + * is usually saved as soon as it changes, so this is just a sanity check + * to make sure there is something on disk before we shutdown. + * + * @return Status object containing non-critical warnings, or an OK status. + */ + protected IStatus saveMetaInfo(Project project, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + //if there is nothing on disk, write the description + if (!workspace.getFileSystemManager().hasSavedDescription(project)) { + workspace.getFileSystemManager().writeSilently(project); + String msg = NLS.bind(Messages.resources_missingProjectMetaRepaired, project.getName()); + return new ResourceStatus(IResourceStatus.MISSING_DESCRIPTION_REPAIRED, project.getFullPath(), msg); + } + if (Policy.DEBUG_SAVE_METAINFO) + Policy.debug("Save metainfo for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return Status.OK_STATUS; + } + + /** + * Writes a snapshot of project refresh information to the specified + * location. + * @param project the project to write a refresh snapshot for + * @param monitor progress monitor + * @exception CoreException if there is a problem writing the snapshot. + */ + public void saveRefreshSnapshot(Project project, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + IFileStore store = EFS.getStore(snapshotLocation); + IPath snapshotPath = new Path(snapshotLocation.getPath()); + java.io.File tmpTree = null; + try { + tmpTree = java.io.File.createTempFile("tmp", ".tree"); //$NON-NLS-1$//$NON-NLS-2$ + } catch (IOException e) { + throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, snapshotPath, Messages.resources_copyProblem, e); + } + ZipOutputStream out = null; + try { + FileOutputStream fis = new FileOutputStream(tmpTree); + DataOutputStream output = new DataOutputStream(fis); + try { + output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeTree(project, output, monitor); + output.close(); + } finally { + FileUtil.safeClose(output); + } + OutputStream snapOut = store.openOutputStream(EFS.NONE, monitor); + out = new ZipOutputStream(snapOut); + out.setLevel(Deflater.BEST_COMPRESSION); + ZipEntry e = new ZipEntry("resource-index.tree"); //$NON-NLS-1$ + out.putNextEntry(e); + int read = 0; + byte[] buffer = new byte[4096]; + InputStream in = new FileInputStream(tmpTree); + try { + while ((read = in.read(buffer)) >= 0) { + out.write(buffer, 0, read); + } + out.closeEntry(); + in.close(); + } finally { + FileUtil.safeClose(in); + } + out.close(); + } catch (IOException e) { + throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, snapshotPath, Messages.resources_copyProblem, e); + } finally { + FileUtil.safeClose(out); + if (tmpTree != null) + tmpTree.delete(); + } + } + + /** + * Writes the current state of the entire workspace tree to disk. + * This is used during workspace save. saveTree(Project) + * is used to save the state of an individual project. + * @exception CoreException if there is a problem writing the tree to disk. + */ + protected void saveTree(Map contexts, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), true); + try { + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + DataOutputStream output = new DataOutputStream(new SafeFileOutputStream(treeLocation.toOSString(), tempLocation.toOSString())); + try { + output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeTree(computeStatesToSave(contexts, workspace.getElementTree()), output, monitor); + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (Exception e) { + String msg = NLS.bind(Messages.resources_writeWorkspaceMeta, treeLocation); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, Path.ROOT, msg, e); + } + if (Policy.DEBUG_SAVE_TREE) + Policy.debug("Save Workspace Tree: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Should only be used for read purposes. + */ + void setPluginsSavedState(HashMap savedStates) { + this.savedStates = Collections.synchronizedMap(savedStates); + } + + protected void setSaveNumber(String pluginId, int number) { + masterTable.setProperty(SAVE_NUMBER_PREFIX + pluginId, Integer.toString(number)); + } + + @Override + public void shareStrings(StringPool pool) { + lastSnap.shareStrings(pool); + } + + @Override + public void shutdown(final IProgressMonitor monitor) { + // do a last snapshot if it was scheduled + // we force it in the same thread because it would not + // help if the job runs after we close the workspace + int state = snapshotJob.getState(); + if (state == Job.WAITING || state == Job.SLEEPING) + // we cannot pass null to Job#run + snapshotJob.run(Policy.monitorFor(monitor)); + // cancel the snapshot job + snapshotJob.cancel(); + } + + /** + * Performs a snapshot if one is deemed necessary. + * Encapsulates rules for determining when a snapshot is needed. + * This should be called at the end of every top level operation. + */ + public void snapshotIfNeeded(boolean hasTreeChanges) { + // never schedule a snapshot while save is occurring. + if (isSaving) + return; + if (snapshotRequested || operationCount >= workspace.internalGetDescription().getOperationsPerSnapshot()) { + rememberSnapshotRequestor(); + if (snapshotJob.getState() == Job.NONE) + snapshotJob.schedule(); + else + snapshotJob.wakeUp(); + } else { + if (hasTreeChanges) { + operationCount++; + if (snapshotJob.getState() == Job.NONE) { + rememberSnapshotRequestor(); + long interval = workspace.internalGetDescription().getSnapshotInterval(); + snapshotJob.schedule(Math.max(interval, MIN_SNAPSHOT_DELAY)); + } + } else { + //only increment the operation count if we've had a sufficient number of no-ops + if (++noopCount > NO_OP_THRESHOLD) { + operationCount++; + noopCount = 0; + } + } + } + } + + /** + * Performs a snapshot of the workspace tree. + */ + protected void snapTree(ElementTree tree, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + //the tree must be immutable + tree.immutable(); + // don't need to snapshot if there are no changes + if (tree == lastSnap) + return; + operationCount = 0; + IPath snapPath = workspace.getMetaArea().getSnapshotLocationFor(workspace.getRoot()); + ElementTreeWriter writer = new ElementTreeWriter(this); + java.io.File localFile = snapPath.toFile(); + try { + SafeChunkyOutputStream safeStream = new SafeChunkyOutputStream(localFile); + DataOutputStream out = new DataOutputStream(safeStream); + try { + out.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeWorkspaceFields(out, monitor); + writer.writeDelta(tree, lastSnap, Path.ROOT, ElementTreeWriter.D_INFINITE, out, ResourceComparator.getSaveComparator()); + safeStream.succeed(); + out.close(); + } finally { + FileUtil.safeClose(out); + } + } catch (IOException e) { + message = NLS.bind(Messages.resources_writeWorkspaceMeta, localFile.getAbsolutePath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, Path.ROOT, message, e); + } + lastSnap = tree; + } finally { + monitor.done(); + } + if (Policy.DEBUG_SAVE_TREE) + Policy.debug("Snapshot Workspace Tree: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Sorts the given array of trees so that the following rules are true: + * - The first tree has no parent + * - No tree has an ancestor with a greater index in the array. + * If there are no missing parents in the given trees array, this means + * that in the resulting array, the i'th tree's parent will be tree i-1. + * The input tree array may contain duplicate trees. + */ + protected ElementTree[] sortTrees(ElementTree[] trees) { + /* the sorted list */ + int numTrees = trees.length; + ElementTree[] sorted = new ElementTree[numTrees]; + + /* first build a table of ElementTree -> List of Integers(indices in trees array) */ + Map> table = new HashMap<>(numTrees * 2 + 1); + for (int i = 0; i < trees.length; i++) { + List indices = table.get(trees[i]); + if (indices == null) { + indices = new ArrayList<>(10); + table.put(trees[i], indices); + } + indices.add(i); + } + + /* find the oldest tree (a descendent of all other trees) */ + ElementTree oldest = trees[ElementTree.findOldest(trees)]; + + /** + * Walk through the chain of trees from oldest to newest, + * adding them to the sorted list as we go. + */ + int i = numTrees - 1; + while (i >= 0) { + /* add all instances of the current oldest tree to the sorted list */ + List indices = table.remove(oldest); + for (Enumeration e = Collections.enumeration(indices); e.hasMoreElements();) { + e.nextElement(); + sorted[i] = oldest; + i--; + } + if (i >= 0) { + /* find the next tree in the list */ + ElementTree parent = oldest.getParent(); + while (parent != null && table.get(parent) == null) { + parent = parent.getParent(); + } + if (parent == null) { + IStatus status = new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, "null parent found while collapsing trees", null); //$NON-NLS-1$ + Policy.log(status); + return null; + } + oldest = parent; + } + } + return sorted; + } + + @Override + public void startup(IProgressMonitor monitor) throws CoreException { + restore(monitor); + java.io.File table = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES).toFile(); + if (!table.exists()) + table.getParentFile().mkdirs(); + } + + /** + * Update the expiration time for the given plug-in's tree. If the tree was never + * loaded, use the current value in the master table. If the tree has been loaded, + * use the provided new timestamp. + * + * The timestamp is used in the policy for cleaning up tree's of plug-ins that are + * not often activated. + */ + protected void updateDeltaExpiration(String pluginId) { + String key = DELTA_EXPIRATION_PREFIX + pluginId; + if (!masterTable.containsKey(key)) + masterTable.setProperty(key, Long.toString(System.currentTimeMillis())); + } + + private void validateMasterTableBeforeSave(java.io.File target) throws IOException { + if (target.exists()) { + MasterTable previousMasterTable = new MasterTable(); + SafeChunkyInputStream input = new SafeChunkyInputStream(target); + try { + previousMasterTable.load(input); + String stringValue = previousMasterTable.getProperty(ROOT_SEQUENCE_NUMBER_KEY); + // if there was a full save, then there must be a non-null entry for root + if (stringValue != null) { + int valueInFile = Integer.parseInt(stringValue); + int valueInMemory = Integer.parseInt(masterTable.getProperty(ROOT_SEQUENCE_NUMBER_KEY)); + // new master table must provide greater or equal sequence number for root + // throw exception if new value is lower than previous one - we cannot allow to desynchronize master table on disk + String message = "Cannot set lower sequence number for root (previous: " + valueInFile + ", new: " + valueInMemory + "). Location: " + target.getAbsolutePath(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Assert.isLegal(valueInMemory >= valueInFile, message); + } + } finally { + input.close(); + } + } + } + + /** + * Visit the given resource (to depth infinite) and write out extra information + * like markers and sync info. To be called during a full save and project save. + * + * FIXME: This method is ugly. Fix it up and look at merging with #visitAndSnap + */ + public void visitAndSave(final IResource root) throws CoreException { + // Ensure we have either a project or the workspace root + Assert.isLegal(root.getType() == IResource.ROOT || root.getType() == IResource.PROJECT); + // only write out info for accessible resources + if (!root.isAccessible()) + return; + + // Setup variables + final Synchronizer synchronizer = (Synchronizer) workspace.getSynchronizer(); + final MarkerManager markerManager = workspace.getMarkerManager(); + IPath markersLocation = workspace.getMetaArea().getMarkersLocationFor(root); + IPath markersTempLocation = workspace.getMetaArea().getBackupLocationFor(markersLocation); + IPath syncInfoLocation = workspace.getMetaArea().getSyncInfoLocationFor(root); + IPath syncInfoTempLocation = workspace.getMetaArea().getBackupLocationFor(syncInfoLocation); + final List writtenTypes = new ArrayList<>(5); + final List writtenPartners = new ArrayList<>(synchronizer.registry.size()); + DataOutputStream o1 = null; + DataOutputStream o2 = null; + String message; + + // Create the output streams + try { + o1 = new DataOutputStream(new SafeFileOutputStream(markersLocation.toOSString(), markersTempLocation.toOSString())); + // we don't store the sync info for the workspace root so don't create + // an empty file + if (root.getType() != IResource.ROOT) + o2 = new DataOutputStream(new SafeFileOutputStream(syncInfoLocation.toOSString(), syncInfoTempLocation.toOSString())); + } catch (IOException e) { + FileUtil.safeClose(o1); + FileUtil.safeClose(o2); + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } + + final DataOutputStream markersOutput = o1; + final DataOutputStream syncInfoOutput = o2; + // The following 2 piece array will hold a running total of the times + // taken to save markers and syncInfo respectively. This will cut down + // on the number of statements printed out as we would get 2 statements + // for each resource otherwise. + final long[] saveTimes = new long[2]; + + // Create the visitor + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) { + ResourceInfo info = (ResourceInfo) elementContents; + if (info != null) { + try { + // save the markers + long start = System.currentTimeMillis(); + markerManager.save(info, requestor, markersOutput, writtenTypes); + long markerSaveTime = System.currentTimeMillis() - start; + saveTimes[0] += markerSaveTime; + persistMarkers += markerSaveTime; + // save the sync info - if we have the workspace root then the output stream will be null + if (syncInfoOutput != null) { + start = System.currentTimeMillis(); + synchronizer.saveSyncInfo(info, requestor, syncInfoOutput, writtenPartners); + long syncInfoSaveTime = System.currentTimeMillis() - start; + saveTimes[1] += syncInfoSaveTime; + persistSyncInfo += syncInfoSaveTime; + } + } catch (IOException e) { + throw new WrappedRuntimeException(e); + } + } + // don't continue if the current resource is the workspace root, only continue for projects + return root.getType() != IResource.ROOT; + } + }; + + // Call the visitor + try { + try { + new ElementTreeIterator(workspace.getElementTree(), root.getFullPath()).iterate(visitor); + } catch (WrappedRuntimeException e) { + throw (IOException) e.getTargetException(); + } + if (Policy.DEBUG_SAVE_MARKERS) + Policy.debug("Save Markers for " + root.getFullPath() + ": " + saveTimes[0] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (Policy.DEBUG_SAVE_SYNCINFO) + Policy.debug("Save SyncInfo for " + root.getFullPath() + ": " + saveTimes[1] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + removeGarbage(markersOutput, markersLocation, markersTempLocation); + // if we have the workspace root the output stream will be null and we + // don't have to perform cleanup code + if (syncInfoOutput != null) { + removeGarbage(syncInfoOutput, syncInfoLocation, syncInfoTempLocation); + syncInfoOutput.close(); + } + markersOutput.close(); + } catch (IOException e) { + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } finally { + FileUtil.safeClose(markersOutput); + FileUtil.safeClose(syncInfoOutput); + } + + // recurse over the projects in the workspace if we were given the workspace root + if (root.getType() == IResource.PROJECT) + return; + IProject[] projects = ((IWorkspaceRoot) root).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + visitAndSave(projects[i]); + } + + /** + * Visit the given resource (to depth infinite) and write out extra information + * like markers and sync info. To be called during a snapshot + * + * FIXME: This method is ugly. Fix it up and look at merging with #visitAndSnap + */ + public void visitAndSnap(final IResource root) throws CoreException { + // Ensure we have either a project or the workspace root + Assert.isLegal(root.getType() == IResource.ROOT || root.getType() == IResource.PROJECT); + // only write out info for accessible resources + if (!root.isAccessible()) + return; + + // Setup variables + final Synchronizer synchronizer = (Synchronizer) workspace.getSynchronizer(); + final MarkerManager markerManager = workspace.getMarkerManager(); + IPath markersLocation = workspace.getMetaArea().getMarkersSnapshotLocationFor(root); + IPath syncInfoLocation = workspace.getMetaArea().getSyncInfoSnapshotLocationFor(root); + SafeChunkyOutputStream safeMarkerStream = null; + SafeChunkyOutputStream safeSyncInfoStream = null; + DataOutputStream o1 = null; + DataOutputStream o2 = null; + String message; + + // Create the output streams + try { + safeMarkerStream = new SafeChunkyOutputStream(markersLocation.toFile()); + o1 = new DataOutputStream(safeMarkerStream); + // we don't store the sync info for the workspace root so don't create + // an empty file + if (root.getType() != IResource.ROOT) { + safeSyncInfoStream = new SafeChunkyOutputStream(syncInfoLocation.toFile()); + o2 = new DataOutputStream(safeSyncInfoStream); + } + } catch (IOException e) { + FileUtil.safeClose(o1); + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } + + final DataOutputStream markersOutput = o1; + final DataOutputStream syncInfoOutput = o2; + int markerFileSize = markersOutput.size(); + int syncInfoFileSize = safeSyncInfoStream == null ? -1 : syncInfoOutput.size(); + // The following 2 piece array will hold a running total of the times + // taken to save markers and syncInfo respectively. This will cut down + // on the number of statements printed out as we would get 2 statements + // for each resource otherwise. + final long[] snapTimes = new long[2]; + + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) { + ResourceInfo info = (ResourceInfo) elementContents; + if (info != null) { + try { + // save the markers + long start = System.currentTimeMillis(); + markerManager.snap(info, requestor, markersOutput); + long markerSnapTime = System.currentTimeMillis() - start; + snapTimes[0] += markerSnapTime; + persistMarkers += markerSnapTime; + // save the sync info - if we have the workspace root then the output stream will be null + if (syncInfoOutput != null) { + start = System.currentTimeMillis(); + synchronizer.snapSyncInfo(info, requestor, syncInfoOutput); + long syncInfoSnapTime = System.currentTimeMillis() - start; + snapTimes[1] += syncInfoSnapTime; + persistSyncInfo += syncInfoSnapTime; + } + } catch (IOException e) { + throw new WrappedRuntimeException(e); + } + } + // don't continue if the current resource is the workspace root, only continue for projects + return root.getType() != IResource.ROOT; + } + }; + + try { + // Call the visitor + try { + new ElementTreeIterator(workspace.getElementTree(), root.getFullPath()).iterate(visitor); + } catch (WrappedRuntimeException e) { + throw (IOException) e.getTargetException(); + } + if (Policy.DEBUG_SAVE_MARKERS) + Policy.debug("Snap Markers for " + root.getFullPath() + ": " + snapTimes[0] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (Policy.DEBUG_SAVE_SYNCINFO) + Policy.debug("Snap SyncInfo for " + root.getFullPath() + ": " + snapTimes[1] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (markerFileSize != markersOutput.size()) + safeMarkerStream.succeed(); + if (safeSyncInfoStream != null && syncInfoFileSize != syncInfoOutput.size()) { + safeSyncInfoStream.succeed(); + syncInfoOutput.close(); + } + markersOutput.close(); + } catch (IOException e) { + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } finally { + FileUtil.safeClose(markersOutput); + FileUtil.safeClose(syncInfoOutput); + } + + // recurse over the projects in the workspace if we were given the workspace root + if (root.getType() == IResource.PROJECT) + return; + IProject[] projects = ((IWorkspaceRoot) root).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + visitAndSnap(projects[i]); + } + + /** + * Writes out persistent information about all builders for which a last built + * tree is available. File format is: + * int - number of builders + * for each builder: + * UTF - project name + * UTF - fully qualified builder extension name + * int - number of interesting projects for builder + * For each interesting project: + * UTF - interesting project name + */ + private void writeBuilderPersistentInfo(DataOutputStream output, List builders, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + // write the number of builders we are saving + int numBuilders = builders.size(); + output.writeInt(numBuilders); + for (int i = 0; i < numBuilders; i++) { + BuilderPersistentInfo info = builders.get(i); + output.writeUTF(info.getProjectName()); + output.writeUTF(info.getBuilderName()); + // write interesting projects + IProject[] interestingProjects = info.getInterestingProjects(); + output.writeInt(interestingProjects.length); + for (int j = 0; j < interestingProjects.length; j++) + output.writeUTF(interestingProjects[j].getName()); + } + } finally { + monitor.done(); + } + } + + @Override + public void writeElement(IPath path, Object element, DataOutput output) throws IOException { + Assert.isNotNull(path); + Assert.isNotNull(element); + Assert.isNotNull(output); + ResourceInfo info = (ResourceInfo) element; + output.writeInt(info.getFlags()); + info.writeTo(output); + } + + /** + * Discovers the trees which need to be saved for the passed in project's builders. + * In a pre-3.7 workspace, only one tree is saved per builder. + * Since 3.7 one tree may be persisted per build configuration per multi-config builder. + * + * We still provide one tree per builder first so the workspace can be opened in an older Eclipse. + * Newer eclipses will be able to load the additional per-configuration trees. + * @param project project to fetch builder trees for + * @param trees list of trees to be persisted + * @param builderInfos list of builder infos; one per builder + * @param configNames configuration names persisted for builder infos above + * @param additionalTrees remaining trees to be persisted for other configurations + * @param additionalBuilderInfos remaining builder infos for other configurations + * @param additionalConfigNames configuration names of the remaining per-configuration trees + * @throws CoreException + */ + private void getTreesToSave(IProject project, List trees, List builderInfos, List configNames, List additionalTrees, List additionalBuilderInfos, List additionalConfigNames) throws CoreException { + if (project.isOpen()) { + String activeConfigName = project.getActiveBuildConfig().getName(); + List infos = workspace.getBuildManager().createBuildersPersistentInfo(project); + if (infos != null) { + for (Iterator it = infos.iterator(); it.hasNext();) { + BuilderPersistentInfo info = it.next(); + // Nothing to persist if there isn't a previous delta tree. + // There used to be code which serialized the current workspace tree + // but this will result in the next build of the builder getting an empty delta... + if (info.getLastBuiltTree() == null) + continue; + + // Add to the correct list of builders info and add to the configuration names + String configName = info.getConfigName() == null ? activeConfigName : info.getConfigName(); + if (configName.equals(activeConfigName)) { + // Serializes the active configurations's build tree + // TODO could probably do better by serializing the 'oldest' tree + builderInfos.add(info); + configNames.add(configName); + trees.add(info.getLastBuiltTree()); + } else { + additionalBuilderInfos.add(info); + additionalConfigNames.add(configName); + additionalTrees.add(info.getLastBuiltTree()); + } + } + } + } + } + + /** + * Attempts to save plugin info, builder info and build states for all projects + * in the workspace. + * + * The following is written to the output stream: + *
            + *
          • Workspace information
          • + *
          • A list of plugin info
          • + *
          • Builder info for all the builders for each project's active build config
          • + *
          • Workspace trees for all plugins and builders
          • + *
          • And since 3.7:
          • + *
          • Builder info for all the builders of all the other project's buildConfigs
          • + *
          • The names of the buildConfigs for each of the builders
          • + *
          + * This format is designed to work with WorkspaceTreeReader versions 2. + * + * @see WorkspaceTreeReader_2 + */ + protected void writeTree(Map statesToSave, DataOutputStream output, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + boolean wasImmutable = false; + try { + // Create an array of trees to save. Ensure that the current one is in the list + ElementTree current = workspace.getElementTree(); + wasImmutable = current.isImmutable(); + current.immutable(); + ArrayList trees = new ArrayList<>(statesToSave.size() * 2); // pick a number + monitor.worked(Policy.totalWork * 10 / 100); + + // write out the workspace fields + writeWorkspaceFields(output, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + // save plugin info + output.writeInt(statesToSave.size()); // write the number of plugins we are saving + for (Map.Entry entry : statesToSave.entrySet()) { + String pluginId = entry.getKey(); + output.writeUTF(pluginId); + trees.add(entry.getValue()); // tree + updateDeltaExpiration(pluginId); + } + monitor.worked(Policy.totalWork * 10 / 100); + + // Get the the builder info and configuration names, and add all the associated workspace trees in the correct order + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List builderInfos = new ArrayList<>(projects.length * 2); + List configNames = new ArrayList<>(projects.length); + List additionalTrees = new ArrayList<>(projects.length * 2); + List additionalBuilderInfos = new ArrayList<>(projects.length * 2); + List additionalConfigNames = new ArrayList<>(projects.length); + for (int i = 0; i < projects.length; i++) + getTreesToSave(projects[i], trees, builderInfos, configNames, additionalTrees, additionalBuilderInfos, additionalConfigNames); + + // Save the version 2 builders info + writeBuilderPersistentInfo(output, builderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 10 / 100)); + + // Builder infos of non-active configurations are persisted after the active + // configuration's builder infos. So, their trees have to follow the same order. + trees.addAll(additionalTrees); + + // add the current tree in the list as the last tree in the chain + trees.add(current); + + /* save the forest! */ + ElementTreeWriter writer = new ElementTreeWriter(this); + ElementTree[] treesToSave = trees.toArray(new ElementTree[trees.size()]); + writer.writeDeltaChain(treesToSave, Path.ROOT, ElementTreeWriter.D_INFINITE, output, ResourceComparator.getSaveComparator()); + monitor.worked(Policy.totalWork * 40 / 100); + + // Since 3.7: Save the additional builders info + writeBuilderPersistentInfo(output, additionalBuilderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 10 / 100)); + + // Save the configuration names for the builders in the order they were saved + for (Iterator it = configNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + for (Iterator it = additionalConfigNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + } finally { + if (!wasImmutable) + workspace.newWorkingTree(); + } + } finally { + monitor.done(); + } + } + + /** + * Attempts to save all the trees for the given project. This includes the current + * workspace tree and a tree for each builder that has previously built state information. + * + * The following is written to the output stream: + *
            + *
          • Builder info for all the builders for the project's active build configuration
          • + *
          • Workspace trees for all the project's builders
          • + *
          • Since 3.7:
          • + *
          • Builder info for all the builders of all the other project's buildConfigs
          • + *
          • Name of the project's buildConfigs
          • + *
          + * This format is designed to work with WorkspaceTreeReader versions 2. + * + * @throws IOException if anything went wrong during save. + * @see WorkspaceTreeReader_2 + */ + protected void writeTree(Project project, DataOutputStream output, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + boolean wasImmutable = false; + try { + // Create an array of trees to save and ensure that the current one is immutable before we add other trees + ElementTree current = workspace.getElementTree(); + wasImmutable = current.isImmutable(); + current.immutable(); + List trees = new ArrayList<>(2); + monitor.worked(Policy.totalWork * 10 / 100); + + // Get the the builder info and configuration names, and add all the associated workspace trees in the correct order + List configNames = new ArrayList<>(5); + List builderInfos = new ArrayList<>(5); + List additionalConfigNames = new ArrayList<>(5); + List additionalBuilderInfos = new ArrayList<>(5); + List additionalTrees = new ArrayList<>(5); + getTreesToSave(project, trees, builderInfos, configNames, additionalTrees, additionalBuilderInfos, additionalConfigNames); + + // Save the version 2 builders info + writeBuilderPersistentInfo(output, builderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 20 / 100)); + + // Builder infos of non-active configurations are persisted after the active + // configuration's builder infos. So, their trees have to follow the same order. + trees.addAll(additionalTrees); + + // Add the current tree in the list as the last tree in the chain + trees.add(current); + + // Save the trees + ElementTreeWriter writer = new ElementTreeWriter(this); + ElementTree[] treesToSave = trees.toArray(new ElementTree[trees.size()]); + writer.writeDeltaChain(treesToSave, project.getFullPath(), ElementTreeWriter.D_INFINITE, output, ResourceComparator.getSaveComparator()); + monitor.worked(Policy.totalWork * 50 / 100); + + // Since 3.7: Save the builders info and get the workspace trees associated with those builders + writeBuilderPersistentInfo(output, additionalBuilderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 20 / 100)); + + // Save configuration names for the builders in the order they were saved + for (Iterator it = configNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + for (Iterator it = additionalConfigNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + } finally { + if (!wasImmutable) + workspace.newWorkingTree(); + } + } finally { + monitor.done(); + } + } + + protected void writeTree(Project project, int depth) throws CoreException { + long start = System.currentTimeMillis(); + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(project, true); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + try { + SafeFileOutputStream safe = new SafeFileOutputStream(treeLocation.toOSString(), tempLocation.toOSString()); + DataOutputStream output = new DataOutputStream(safe); + try { + output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeTree(project, output, null); + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (IOException e) { + String msg = NLS.bind(Messages.resources_writeMeta, project.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, treeLocation, msg, e); + } + if (Policy.DEBUG_SAVE_TREE) + Policy.debug("Save tree for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + protected void writeWorkspaceFields(DataOutputStream output, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + // save the next node id + output.writeLong(workspace.nextNodeId); + // save the modification stamp (no longer used) + output.writeLong(0L); + // save the marker id counter + output.writeLong(workspace.nextMarkerId); + // save the registered sync partners in the synchronizer + ((Synchronizer) workspace.getSynchronizer()).savePartners(output); + } finally { + monitor.done(); + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java new file mode 100644 index 0000000000..6b3c24c305 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.internal.events.ResourceDelta; +import org.eclipse.core.internal.events.ResourceDeltaFactory; +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.ISchedulingRule; + +/** + * Standard implementation of the ISavedState interface. + */ +public class SavedState implements ISavedState { + ElementTree oldTree; + ElementTree newTree; + SafeFileTable fileTable; + String pluginId; + Workspace workspace; + + SavedState(Workspace workspace, String pluginId, ElementTree oldTree, ElementTree newTree) throws CoreException { + this.workspace = workspace; + this.pluginId = pluginId; + this.newTree = newTree; + this.oldTree = oldTree; + this.fileTable = restoreFileTable(); + } + + void forgetTrees() { + newTree = null; + oldTree = null; + workspace.saveManager.clearDeltaExpiration(pluginId); + } + + @Override + public int getSaveNumber() { + return workspace.getSaveManager().getSaveNumber(pluginId); + } + + protected SafeFileTable getFileTable() { + return fileTable; + } + + protected SafeFileTable restoreFileTable() throws CoreException { + if (fileTable == null) + fileTable = new SafeFileTable(pluginId); + return fileTable; + } + + @Override + public IPath lookup(IPath file) { + return getFileTable().lookup(file); + } + + @Override + public IPath[] getFiles() { + return getFileTable().getFiles(); + } + + @Override + public void processResourceChangeEvents(IResourceChangeListener listener) { + try { + final ISchedulingRule rule = workspace.getRoot(); + try { + workspace.prepareOperation(rule, null); + if (oldTree == null || newTree == null) + return; + workspace.beginOperation(true); + ResourceDelta delta = ResourceDeltaFactory.computeDelta(workspace, oldTree, newTree, Path.ROOT, -1); + forgetTrees(); // free trees to prevent memory leak + workspace.getNotificationManager().broadcastChanges(listener, IResourceChangeEvent.POST_BUILD, delta); + } finally { + workspace.endOperation(rule, false, null); + } + } catch (CoreException e) { + // this is unlikely to happen, so, just log it + Policy.log(e); + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java new file mode 100644 index 0000000000..c9ed601627 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.osgi.util.NLS; + +/** + * This class is used to read sync info from disk. Subclasses implement + * version specific reading code. + */ +public class SyncInfoReader { + protected Workspace workspace; + protected Synchronizer synchronizer; + + public SyncInfoReader(Workspace workspace, Synchronizer synchronizer) { + super(); + this.workspace = workspace; + this.synchronizer = synchronizer; + } + + /** + * Returns the appropriate reader for the given version. + */ + protected SyncInfoReader getReader(int formatVersion) throws IOException { + switch (formatVersion) { + case 2 : + return new SyncInfoReader_2(workspace, synchronizer); + case 3 : + return new SyncInfoReader_3(workspace, synchronizer); + default : + throw new IOException(NLS.bind(Messages.resources_format, formatVersion)); + } + } + + public void readPartners(DataInputStream input) throws CoreException { + try { + int size = input.readInt(); + Set registry = new HashSet<>(size); + for (int i = 0; i < size; i++) { + String qualifier = input.readUTF(); + String local = input.readUTF(); + registry.add(new QualifiedName(qualifier, local)); + } + synchronizer.setRegistry(registry); + } catch (IOException e) { + String message = NLS.bind(Messages.resources_readSync, e); + throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, message)); + } + } + + public void readSyncInfo(DataInputStream input) throws IOException, CoreException { + // dispatch to the appropriate reader depending + // on the version of the file + int formatVersion = readVersionNumber(input); + SyncInfoReader reader = getReader(formatVersion); + reader.readSyncInfo(input); + } + + protected static int readVersionNumber(DataInputStream input) throws IOException { + return input.readInt(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java new file mode 100644 index 0000000000..105db79654 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.ObjectMap; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * This class is used to read sync info from disk. This is the implementation + * for reading files with version number 2. + */ +public class SyncInfoReader_2 extends SyncInfoReader { + + // for sync info + public static final int INDEX = 1; + public static final int QNAME = 2; + + public SyncInfoReader_2(Workspace workspace, Synchronizer synchronizer) { + super(workspace, synchronizer); + } + + /** + * SAVE_FILE -> VERSION_ID RESOURCE* + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> TYPE BYTES + * TYPE -> INDEX | QNAME + * INDEX -> int int + * QNAME -> int String + * BYTES -> byte[] + * + */ + @Override + public void readSyncInfo(DataInputStream input) throws IOException, CoreException { + try { + List readPartners = new ArrayList<>(5); + while (true) { + IPath path = new Path(input.readUTF()); + readSyncInfo(path, input, readPartners); + } + } catch (EOFException e) { + // ignore end of file + } + } + + private void readSyncInfo(IPath path, DataInputStream input, List readPartners) throws IOException, CoreException { + int size = input.readInt(); + ObjectMap table = new ObjectMap<>(size); + for (int i = 0; i < size; i++) { + QualifiedName name = null; + int type = input.readInt(); + switch (type) { + case QNAME : + String qualifier = input.readUTF(); + String local = input.readUTF(); + name = new QualifiedName(qualifier, local); + readPartners.add(name); + break; + case INDEX : + name = readPartners.get(input.readInt()); + break; + default : + //if we get here then the sync info file is corrupt + String msg = NLS.bind(Messages.resources_readSync, path == null ? "" : path.toString()); //$NON-NLS-1$ + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, path, msg, null); + } + // read the bytes + int length = input.readInt(); + byte[] bytes = new byte[length]; + input.readFully(bytes); + // put them in the table + table.put(name, bytes); + } + // set the table on the resource info + ResourceInfo info = workspace.getResourceInfo(path, true, false); + if (info == null) + return; + info.setSyncInfo(table); + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java new file mode 100644 index 0000000000..263e6303ca --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.ObjectMap; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * This class is used to read sync info from disk. This is the implementation + * for reading files with version number 3. + */ +public class SyncInfoReader_3 extends SyncInfoReader { + + // for sync info + public static final byte INDEX = 1; + public static final byte QNAME = 2; + + public SyncInfoReader_3(Workspace workspace, Synchronizer synchronizer) { + super(workspace, synchronizer); + } + + /** + * SAVE_FILE -> VERSION_ID RESOURCE+ + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> TYPE BYTES + * TYPE -> INDEX | QNAME + * INDEX -> byte int + * QNAME -> byte String + * BYTES -> byte[] + * + */ + @Override + public void readSyncInfo(DataInputStream input) throws IOException, CoreException { + try { + List readPartners = new ArrayList<>(5); + while (true) { + IPath path = new Path(input.readUTF()); + readSyncInfo(path, input, readPartners); + } + } catch (EOFException e) { + // ignore end of file + } + } + + private void readSyncInfo(IPath path, DataInputStream input, List readPartners) throws IOException, CoreException { + int size = input.readInt(); + ObjectMap table = new ObjectMap<>(size); + for (int i = 0; i < size; i++) { + QualifiedName name = null; + byte type = input.readByte(); + switch (type) { + case QNAME : + String qualifier = input.readUTF(); + String local = input.readUTF(); + name = new QualifiedName(qualifier, local); + readPartners.add(name); + break; + case INDEX : + name = readPartners.get(input.readInt()); + break; + default : + //if we get here then the sync info file is corrupt + String msg = NLS.bind(Messages.resources_readSync, path == null ? "" : path.toString()); //$NON-NLS-1$ + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, path, msg, null); + } + // read the bytes + int length = input.readInt(); + byte[] bytes = new byte[length]; + input.readFully(bytes); + // put them in the table + table.put(name, bytes); + } + // set the table on the resource info + ResourceInfo info = workspace.getResourceInfo(path, true, false); + if (info == null) + return; + info.setSyncInfo(table); + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java new file mode 100644 index 0000000000..812f36703f --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import java.io.IOException; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.osgi.util.NLS; + +public class SyncInfoSnapReader { + protected Workspace workspace; + protected Synchronizer synchronizer; + + public SyncInfoSnapReader(Workspace workspace, Synchronizer synchronizer) { + super(); + this.workspace = workspace; + this.synchronizer = synchronizer; + } + + /** + * Returns the appropriate reader for the given version. + */ + protected SyncInfoSnapReader getReader(int formatVersion) throws IOException { + switch (formatVersion) { + case 3 : + return new SyncInfoSnapReader_3(workspace, synchronizer); + default : + throw new IOException(NLS.bind(Messages.resources_format, formatVersion)); + } + } + + public void readSyncInfo(DataInputStream input) throws IOException { + // dispatch to the appropriate reader depending + // on the version of the file + int formatVersion = readVersionNumber(input); + SyncInfoSnapReader reader = getReader(formatVersion); + reader.readSyncInfo(input); + } + + protected static int readVersionNumber(DataInputStream input) throws IOException { + return input.readInt(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java new file mode 100644 index 0000000000..86a7b05f03 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import java.io.IOException; +import org.eclipse.core.internal.utils.ObjectMap; +import org.eclipse.core.runtime.*; + +public class SyncInfoSnapReader_3 extends SyncInfoSnapReader { + + public SyncInfoSnapReader_3(Workspace workspace, Synchronizer synchronizer) { + super(workspace, synchronizer); + } + + private ObjectMap internalReadSyncInfo(DataInputStream input) throws IOException { + int size = input.readInt(); + ObjectMap map = new ObjectMap<>(size); + for (int i = 0; i < size; i++) { + // read the qualified name + String qualifier = input.readUTF(); + String local = input.readUTF(); + QualifiedName name = new QualifiedName(qualifier, local); + // read the bytes + int length = input.readInt(); + byte[] bytes = new byte[length]; + input.readFully(bytes); + // put them in the table + map.put(name, bytes); + } + return map; + } + + /** + * SNAP_FILE -> [VERSION_ID RESOURCE]* + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> QNAME BYTES + * QNAME -> String String + * BYTES -> byte[] + */ + @Override + public void readSyncInfo(DataInputStream input) throws IOException { + IPath path = new Path(input.readUTF()); + ObjectMap map = internalReadSyncInfo(input); + // set the table on the resource info + ResourceInfo info = workspace.getResourceInfo(path, true, false); + if (info == null) + return; + info.setSyncInfo(map); + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java new file mode 100644 index 0000000000..55792833f2 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.resources; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; +import org.eclipse.core.internal.watson.IPathRequestor; +import org.eclipse.core.runtime.QualifiedName; + +public class SyncInfoWriter { + protected Synchronizer synchronizer; + protected Workspace workspace; + + // version number + public static final int SYNCINFO_SAVE_VERSION = 3; + public static final int SYNCINFO_SNAP_VERSION = 3; + + // for sync info + public static final byte INDEX = 1; + public static final byte QNAME = 2; + + public SyncInfoWriter(Workspace workspace, Synchronizer synchronizer) { + super(); + this.workspace = workspace; + this.synchronizer = synchronizer; + } + + public void savePartners(DataOutputStream output) throws IOException { + Set registry = synchronizer.getRegistry(); + output.writeInt(registry.size()); + for (Iterator i = registry.iterator(); i.hasNext();) { + QualifiedName qname = i.next(); + output.writeUTF(qname.getQualifier()); + output.writeUTF(qname.getLocalName()); + } + } + + /** + * SAVE_FILE -> VERSION_ID RESOURCE+ + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> TYPE BYTES + * TYPE -> INDEX | QNAME + * INDEX -> byte int + * QNAME -> byte String + * BYTES -> byte[] + */ + public void saveSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List writtenPartners) throws IOException { + Map table = info.getSyncInfo(false); + if (table == null) + return; + // if this is the first sync info that we have written, then + // write the version id for the file. + if (output.size() == 0) + output.writeInt(SYNCINFO_SAVE_VERSION); + output.writeUTF(requestor.requestPath().toString()); + output.writeInt(table.size()); + for (Map.Entry entry : table.entrySet()) { + QualifiedName name = entry.getKey(); + // if we have already written the partner name once, then write an integer + // constant to represent it instead to remove duplication + int index = writtenPartners.indexOf(name); + if (index == -1) { + // FIXME: what to do about null qualifier? + output.writeByte(QNAME); + output.writeUTF(name.getQualifier()); + output.writeUTF(name.getLocalName()); + writtenPartners.add(name); + } else { + output.writeByte(INDEX); + output.writeInt(index); + } + byte[] bytes = (byte[]) entry.getValue(); + output.writeInt(bytes.length); + output.write(bytes); + } + } + + /** + * SNAP_FILE -> [VERSION_ID RESOURCE]* + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> QNAME BYTES + * QNAME -> String String + * BYTES -> byte[] + */ + public void snapSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { + if (!info.isSet(ICoreConstants.M_SYNCINFO_SNAP_DIRTY)) + return; + Map table = info.getSyncInfo(false); + if (table == null) + return; + // write the version id for the snapshot. + output.writeInt(SYNCINFO_SNAP_VERSION); + output.writeUTF(requestor.requestPath().toString()); + output.writeInt(table.size()); + for (Map.Entry entry : table.entrySet()) { + QualifiedName name = entry.getKey(); + output.writeUTF(name.getQualifier()); + output.writeUTF(name.getLocalName()); + byte[] bytes = (byte[]) entry.getValue(); + output.writeInt(bytes.length); + output.write(bytes); + } + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java new file mode 100644 index 0000000000..47d5873bf7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java @@ -0,0 +1,285 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +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.IPathRequestor; +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 Synchronizer implements ISynchronizer { + protected Workspace workspace; + protected SyncInfoWriter writer; + + // Registry of sync partners. Set of qualified names. + protected Set registry = new HashSet<>(5); + + public Synchronizer(Workspace workspace) { + super(); + this.workspace = workspace; + this.writer = new SyncInfoWriter(workspace, this); + } + + /** + * @see ISynchronizer#accept(QualifiedName, IResource, IResourceVisitor, int) + */ + @Override + public void accept(QualifiedName partner, IResource resource, IResourceVisitor visitor, int depth) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(resource != null); + Assert.isLegal(visitor != null); + + // if we don't have sync info for the given identifier, then skip it + if (getSyncInfo(partner, resource) != null) { + // visit the resource and if the visitor says to stop the recursion then return + if (!visitor.visit(resource)) + return; + } + + // adjust depth if necessary + if (depth == IResource.DEPTH_ZERO || resource.getType() == IResource.FILE) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.DEPTH_ZERO; + + // otherwise recurse over the children + IResource[] children = ((IContainer) resource).members(); + for (int i = 0; i < children.length; i++) + accept(partner, children[i], visitor, depth); + } + + /** + * @see ISynchronizer#add(QualifiedName) + */ + @Override + public void add(QualifiedName partner) { + Assert.isLegal(partner != null); + registry.add(partner); + } + + /** + * @see ISynchronizer#flushSyncInfo(QualifiedName, IResource, int) + */ + @Override + public void flushSyncInfo(final QualifiedName partner, final IResource root, final int depth) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(root != null); + + ICoreRunnable body = new ICoreRunnable() { + @Override + public void run(IProgressMonitor monitor) throws CoreException { + IResourceVisitor visitor = new IResourceVisitor() { + @Override + public boolean visit(IResource resource) throws CoreException { + //only need to flush sync info if there is sync info + if (getSyncInfo(partner, resource) != null) + setSyncInfo(partner, resource, null); + return true; + } + }; + root.accept(visitor, depth, true); + } + }; + workspace.run(body, root, IResource.NONE, null); + } + + /** + * @see ISynchronizer#getPartners() + */ + @Override + public QualifiedName[] getPartners() { + return registry.toArray(new QualifiedName[registry.size()]); + } + + /** + * For use by the serialization code. + */ + protected Set getRegistry() { + return registry; + } + + /** + * @see ISynchronizer#getSyncInfo(QualifiedName, IResource) + */ + @Override + public byte[] getSyncInfo(QualifiedName partner, IResource resource) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(resource != null); + + if (!isRegistered(partner)) { + String message = NLS.bind(Messages.synchronizer_partnerNotRegistered, partner); + throw new ResourceException(new ResourceStatus(IResourceStatus.PARTNER_NOT_REGISTERED, message)); + } + + // namespace check, if the resource doesn't exist then return null + ResourceInfo info = workspace.getResourceInfo(resource.getFullPath(), true, false); + return (info == null) ? null : info.getSyncInfo(partner, true); + } + + protected boolean isRegistered(QualifiedName partner) { + Assert.isLegal(partner != null); + return registry.contains(partner); + } + + /** + * @see #savePartners(DataOutputStream) + */ + public void readPartners(DataInputStream input) throws CoreException { + SyncInfoReader reader = new SyncInfoReader(workspace, this); + reader.readPartners(input); + } + + public void restore(IResource resource, IProgressMonitor monitor) throws CoreException { + // first restore from the last save and then apply any snapshots + restoreFromSave(resource); + restoreFromSnap(resource); + } + + protected void restoreFromSave(IResource resource) throws CoreException { + IPath sourceLocation = workspace.getMetaArea().getSyncInfoLocationFor(resource); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(sourceLocation); + if (!sourceLocation.toFile().exists() && !tempLocation.toFile().exists()) + return; + try { + DataInputStream input = new DataInputStream(new SafeFileInputStream(sourceLocation.toOSString(), tempLocation.toOSString())); + try { + SyncInfoReader reader = new SyncInfoReader(workspace, this); + reader.readSyncInfo(input); + } 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().getSyncInfoSnapshotLocationFor(resource); + if (!sourceLocation.toFile().exists()) + return; + try { + DataInputStream input = new DataInputStream(new SafeChunkyInputStream(sourceLocation.toFile())); + try { + SyncInfoSnapReader reader = new SyncInfoSnapReader(workspace, this); + while (true) + reader.readSyncInfo(input); + } catch (EOFException eof) { + // ignore end of file -- proceed with what we successfully read + } 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)); + } + } + + /** + * @see ISynchronizer#remove(QualifiedName) + */ + @Override + public void remove(QualifiedName partner) { + Assert.isLegal(partner != null); + if (isRegistered(partner)) { + // remove all sync info for this partner + try { + flushSyncInfo(partner, workspace.getRoot(), IResource.DEPTH_INFINITE); + registry.remove(partner); + } catch (CoreException e) { + // XXX: flush needs to be more resilient and not throw exceptions all the time + Policy.log(e); + } + } + } + + public void savePartners(DataOutputStream output) throws IOException { + writer.savePartners(output); + } + + public void saveSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List writtenPartners) throws IOException { + writer.saveSyncInfo(info, requestor, output, writtenPartners); + } + + protected void setRegistry(Set registry) { + this.registry = registry; + } + + /** + * @see ISynchronizer#setSyncInfo(QualifiedName, IResource, byte[]) + */ + @Override + public void setSyncInfo(QualifiedName partner, IResource resource, byte[] info) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(resource != null); + /* + * We use a dedicated sync rule for this operation and not the resoure + * itself directly. This helps to avoid cancelled auto builds when team provider + * trigger a #setSyncInfo from within a build, e.g. when a builder generates code + * into directories, that are monitored by the team provider. + */ + ISchedulingRule syncRule = workspace.getRuleFactory().syncInfoRule(resource); + try { + workspace.prepareOperation(syncRule, null); + workspace.beginOperation(true); + if (!isRegistered(partner)) { + String message = NLS.bind(Messages.synchronizer_partnerNotRegistered, partner); + throw new ResourceException(new ResourceStatus(IResourceStatus.PARTNER_NOT_REGISTERED, message)); + } + // we do not store sync info on the workspace root + if (resource.getType() == IResource.ROOT) + return; + // if the resource doesn't yet exist then create a phantom so we can set the sync info on it + Resource target = (Resource) resource; + ResourceInfo resourceInfo = workspace.getResourceInfo(target.getFullPath(), true, false); + int flags = target.getFlags(resourceInfo); + if (!target.exists(flags, false)) { + if (info == null) + return; + //ensure it is possible to create this resource + target.checkValidPath(target.getFullPath(), target.getType(), false); + Container parent = (Container) target.getParent(); + parent.checkAccessible(parent.getFlags(parent.getResourceInfo(true, false))); + workspace.createResource(target, true); + } + resourceInfo = target.getResourceInfo(true, true); + resourceInfo.setSyncInfo(partner, info); + resourceInfo.incrementSyncInfoGenerationCount(); + resourceInfo.set(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + flags = target.getFlags(resourceInfo); + if (target.isPhantom(flags) && resourceInfo.getSyncInfo(false) == null) { + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.resources_deleteProblem, null); + ((Resource) resource).deleteResource(false, status); + if (!status.isOK()) + throw new ResourceException(status); + } + } finally { + workspace.endOperation(syncRule, false, null); + } + } + + public void snapSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { + writer.snapSyncInfo(info, requestor, output); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java new file mode 100644 index 0000000000..cc1763b518 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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 java.util.Properties; +import org.eclipse.core.resources.ResourcesPlugin; + +/** + * Provides special internal access to the workspace resource implementation. + * This class is to be used for testing purposes only. + * + * @since 2.0 + */ +public class TestingSupport { + /** + * Returns the save manager's master table. + */ + public static Properties getMasterTable() { + return ((Workspace) ResourcesPlugin.getWorkspace()).getSaveManager().getMasterTable(); + } + + /** + * Blocks the calling thread until background snapshot completes. + * @since 3.0 + */ + public static void waitForSnapshot() { + try { + ((Workspace) ResourcesPlugin.getWorkspace()).getSaveManager().snapshotJob.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + throw new RuntimeException("Interrupted while waiting for snapshot"); //$NON-NLS-1$ + } + } + + /* + * Class cannot be instantiated. + */ + private TestingSupport() { + // not allowed + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java new file mode 100644 index 0000000000..aaa9dbdc12 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import org.eclipse.core.runtime.Assert; + +/** + * + */ +public class VariableDescription implements Comparable { + private String name; + private String value; + + public VariableDescription() { + this.name = ""; //$NON-NLS-1$ + this.value = ""; //$NON-NLS-1$ + } + + public VariableDescription(String name, String value) { + super(); + Assert.isNotNull(name); + Assert.isNotNull(value); + this.name = name; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (!(o.getClass() == VariableDescription.class)) + return false; + VariableDescription other = (VariableDescription) o; + return name.equals(other.name) && value == other.value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + @Override + public int hashCode() { + return name.hashCode() + value.hashCode(); + } + + public void setName(String name) { + this.name = name; + } + + public void setValue(String value) { + this.value = value; + } + + /** + * Compare string descriptions in a way that sorts them topologically by + * name. + */ + @Override + public int compareTo(VariableDescription that) { + return name.compareTo(that.name); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java new file mode 100644 index 0000000000..577ad88e35 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2009, 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 - Fix for bug 266712 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.InputStream; +import java.net.URI; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.filesystem.provider.FileStore; +import org.eclipse.core.runtime.*; + +/** + * A file store representing a virtual resource. + * A virtual resource always exists and has no children. + */ +public class VirtualFileStore extends FileStore { + private final URI location; + + public VirtualFileStore(URI location) { + this.location = location; + } + + @Override + public String[] childNames(int options, IProgressMonitor monitor) { + return FileStore.EMPTY_STRING_ARRAY; + } + + @Override + public IFileInfo fetchInfo(int options, IProgressMonitor monitor) { + FileInfo result = new FileInfo(); + result.setDirectory(true); + result.setExists(true); + result.setLastModified(1);//last modified of zero indicates non-existence + return result; + } + + @Override + public void delete(int options, IProgressMonitor monitor) { + //nothing to do - virtual resources don't exist in any physical file system + } + + @Override + public IFileStore getChild(String name) { + return EFS.getNullFileSystem().getStore(new Path(name).makeAbsolute()); + } + + @Override + public String getName() { + return "virtual"; //$NON-NLS-1$ + } + + @Override + public IFileStore getParent() { + return null; + } + + @Override + public void move(IFileStore destination, int options, IProgressMonitor monitor) throws CoreException { + destination.mkdir(EFS.NONE, monitor); + } + + @Override + public InputStream openInputStream(int options, IProgressMonitor monitor) { + return null; + } + + @Override + public URI toURI() { + return location; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java new file mode 100644 index 0000000000..500d0249cf --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2009, 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 java.net.URI; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.filesystem.provider.FileSystem; + +/** + * A file system for virtual resources + */ +public class VirtualFileSystem extends FileSystem { + + public VirtualFileSystem() { + super(); + } + + @Override + public IFileStore getStore(URI uri) { + return new VirtualFileStore(uri); + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java new file mode 100644 index 0000000000..2a3d300c83 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java @@ -0,0 +1,325 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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.IResource; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.*; + +/** + * The work manager governs concurrent access to the workspace tree. The {@link #lock} + * field is used to protect the workspace tree data structure from concurrent + * write attempts. This is an internal lock that is generally not held while + * client code is running. Scheduling rules are used by client code to obtain + * exclusive write access to a portion of the workspace. + * + * This class also tracks operation state for each thread that is involved in an + * operation. This includes prepared and running operation depth, auto-build + * strategy and cancel state. + */ +public class WorkManager implements IManager { + /** + * Scheduling rule for use during resource change notification. This rule + * must always be allowed to nest within a resource rule of any granularity + * since it is used from within the scope of all resource changing + * operations. The purpose of this rule is two-fold: 1. To prevent other + * resource changing jobs from being scheduled while the notification is + * running 2. To cause an exception if a resource change listener tries to + * begin a resource rule during a notification. This also prevents + * deadlock, because the notification thread owns the workspace lock, and + * threads that own the workspace lock must never block trying to acquire a + * resource rule. + */ + class NotifyRule implements ISchedulingRule { + @Override + public boolean contains(ISchedulingRule rule) { + return (rule instanceof IResource) || rule.getClass().equals(NotifyRule.class); + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + return contains(rule); + } + } + + /** + * Indicates that the last checkIn failed, either due to cancelation or due to the + * workspace tree being locked for modifications (during resource change events). + */ + private final ThreadLocal checkInFailed = new ThreadLocal<>(); + /** + * Indicates whether any operations have run that may require a build. + */ + private boolean hasBuildChanges = false; + private IJobManager jobManager; + /** + * The primary workspace lock. This lock must be held by any thread + * modifying the workspace tree. + */ + private final ILock lock; + + /** + * The current depth of running nested operations. + */ + private int nestedOperations = 0; + + private NotifyRule notifyRule = new NotifyRule(); + + private boolean operationCanceled = false; + + /** + * The current depth of prepared operations. + */ + private int preparedOperations = 0; + private Workspace workspace; + + public WorkManager(Workspace workspace) { + this.workspace = workspace; + this.jobManager = Job.getJobManager(); + this.lock = jobManager.newLock(); + } + + /** + * Releases the workspace lock without changing the nested operation depth. + * Must be followed eventually by endUnprotected. Any + * beginUnprotected/endUnprotected pair must be done entirely within the + * scope of a checkIn/checkOut pair. Returns the old lock depth. + * @see #endUnprotected(int) + */ + public int beginUnprotected() { + int depth = lock.getDepth(); + for (int i = 0; i < depth; i++) + lock.release(); + return depth; + } + + /** + * An operation calls this method and it only returns when the operation is + * free to run. + */ + public void checkIn(ISchedulingRule rule, IProgressMonitor monitor) throws CoreException { + boolean success = false; + try { + if (workspace.isTreeLocked()) { + String msg = Messages.resources_cannotModify; + throw new ResourceException(IResourceStatus.WORKSPACE_LOCKED, null, msg, null); + } + jobManager.beginRule(rule, monitor); + lock.acquire(); + incrementPreparedOperations(); + success = true; + } finally { + //remember if we failed to check in, so we can avoid check out + if (!success) + checkInFailed.set(Boolean.TRUE); + } + } + + /** + * Returns true if the check in for this thread failed, in which case the + * check out and other end of operation code should not run. + *

          + * The failure flag is reset immediately after calling this method. Subsequent + * calls to this method will indicate no failure (unless a new failure has occurred). + * @return true if the checkIn failed, and false otherwise. + */ + public boolean checkInFailed(ISchedulingRule rule) { + if (checkInFailed.get() != null) { + //clear the failure flag for this thread + checkInFailed.set(null); + //must still end the rule even in the case of failure + if (!workspace.isTreeLocked()) + jobManager.endRule(rule); + return true; + } + return false; + } + + /** + * Inform that an operation has finished. + */ + public synchronized void checkOut(ISchedulingRule rule) { + decrementPreparedOperations(); + rebalanceNestedOperations(); + //reset state if this is the end of a top level operation + if (preparedOperations == 0) + hasBuildChanges = false; + //don't let cancelation of this operation affect other operations + operationCanceled = false; + try { + lock.release(); + } finally { + //end rule in finally in case lock.release throws an exception + jobManager.endRule(rule); + } + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + private void decrementPreparedOperations() { + preparedOperations--; + } + + /** + * Re-acquires the workspace lock that was temporarily released during an + * operation, and restores the old lock depth. + * @see #beginUnprotected() + */ + public void endUnprotected(int depth) { + for (int i = 0; i < depth; i++) + lock.acquire(); + } + + /** + * Returns the work manager's lock + */ + ILock getLock() { + return lock; + } + + /** + * Returns the scheduling rule used during resource change notifications. + */ + public ISchedulingRule getNotifyRule() { + return notifyRule; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + public synchronized int getPreparedOperationDepth() { + return preparedOperations; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + void incrementNestedOperations() { + nestedOperations++; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + private void incrementPreparedOperations() { + preparedOperations++; + } + + /** + * Returns true if the nested operation depth is the same as the prepared + * operation depth, and false otherwise. This method can only be safely + * called from inside a workspace operation. Should NOT be called from + * outside a prepareOperation/endOperation block. + */ + boolean isBalanced() { + return nestedOperations == preparedOperations; + } + + /** + * Returns true if the workspace lock has already been acquired by this + * thread, and false otherwise. + */ + public boolean isLockAlreadyAcquired() { + boolean result = false; + try { + boolean success = lock.acquire(0L); + if (success) { + //if lock depth is greater than one, then we already owned it + // before + result = lock.getDepth() > 1; + lock.release(); + } + } catch (InterruptedException e) { + // ignore + } + return result; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + public void operationCanceled() { + operationCanceled = true; + } + + /** + * Used to make things stable again after an operation has failed between a + * workspace.prepareOperation() and workspace.beginOperation(). This method + * can only be safely called from inside a workspace operation. Should NOT + * be called from outside a prepareOperation/endOperation block. + */ + public void rebalanceNestedOperations() { + nestedOperations = preparedOperations; + } + + /** + * Indicates if the operation that has just completed may potentially + * require a build. + */ + public void setBuild(boolean hasChanges) { + hasBuildChanges = hasBuildChanges || hasChanges; + } + + /** + * This method can only be safely called from inside a workspace operation. + * Should NOT be called from outside a prepareOperation/endOperation block. + */ + public boolean shouldBuild() { + if (hasBuildChanges) { + if (operationCanceled) + return Policy.buildOnCancel; + return true; + } + return false; + } + + @Override + public void shutdown(IProgressMonitor monitor) { + // do nothing + } + + @Override + public void startup(IProgressMonitor monitor) { + jobManager.beginRule(workspace.getRoot(), monitor); + lock.acquire(); + } + + /** + * This method should be called at the end of the workspace startup, even if the startup failed. + * It must be preceded by a call to startup. It releases the primary workspace lock + * and ends applying the workspace rule to this thread. + */ + void postWorkspaceStartup() { + try { + lock.release(); + } finally { + //end rule in finally in case lock.release throws an exception + jobManager.endRule(workspace.getRoot()); + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java new file mode 100644 index 0000000000..28aea77d4f --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java @@ -0,0 +1,2571 @@ +/******************************************************************************* + * 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 + * Red Hat Incorporated - loadProjectDescription(InputStream) + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + * Broadcom Corporation - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.*; +import org.eclipse.core.internal.localstore.FileSystemResourceManager; +import org.eclipse.core.internal.properties.IPropertyManager; +import org.eclipse.core.internal.properties.PropertyManager2; +import org.eclipse.core.internal.refresh.RefreshManager; +import org.eclipse.core.internal.resources.ComputeProjectOrder.VertexFilter; +import org.eclipse.core.internal.resources.ComputeProjectOrder.VertexOrder; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.internal.watson.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.Bundle; +import org.xml.sax.InputSource; + +/** + * The workspace class is the monolithic nerve center of the resources plugin. + * All interesting functionality stems from this class. + *

          + *

          + * The lifecycle of the resources plugin is encapsulated by the {@link #open(IProgressMonitor)} + * and {@link #close(IProgressMonitor)} methods. A closed workspace is completely + * unusable - any attempt to access or modify interesting workspace state on a closed + * workspace will fail. + *

          + *

          + * All modifications to the workspace occur within the context of a workspace operation. + * A workspace operation is implemented using the following sequence: + *

          + * 	try {
          + *		prepareOperation(...);
          + *		//check preconditions
          + *		beginOperation(...);
          + *		//perform changes
          + *	} finally {
          + *		endOperation(...);
          + *	}
          + * 
          + * Workspace operations can be nested arbitrarily. A "top level" workspace operation + * is an operation that is not nested within another workspace operation in the current + * thread. + * See the javadoc of {@link #prepareOperation(ISchedulingRule, IProgressMonitor)}, + * {@link #beginOperation(boolean)}, and {@link #endOperation(ISchedulingRule, boolean, IProgressMonitor)} + * for more details. + *

          + *

          + * Major areas of functionality are farmed off to various manager classes. Open a + * type hierarchy on {@link IManager} to see all the different managers. Each + * manager is typically referenced three times in this class: Once in {@link #startup(IProgressMonitor)} + * when it is instantiated, once in {@link #shutdown(IProgressMonitor)} when it + * is destroyed, and once in a manager accessor method. + *

          + */ +public class Workspace extends PlatformObject implements IWorkspace, ICoreConstants { + public static final boolean caseSensitive = Platform.OS_MACOSX.equals(Platform.getOS()) ? false : new java.io.File("a").compareTo(new java.io.File("A")) != 0; //$NON-NLS-1$ //$NON-NLS-2$ + + /** + * Work manager should never be accessed directly because accessor + * asserts that workspace is still open. + */ + protected WorkManager _workManager; + protected AliasManager aliasManager; + protected BuildManager buildManager; + protected volatile IBuildConfiguration[] buildOrder = null; + protected CharsetManager charsetManager; + protected ContentDescriptionManager contentDescriptionManager; + /** indicates if the workspace crashed in a previous session */ + protected boolean crashed = false; + protected final IWorkspaceRoot defaultRoot = new WorkspaceRoot(Path.ROOT, this); + protected WorkspacePreferences description; + protected FileSystemResourceManager fileSystemManager; + protected final CopyOnWriteArrayList lifecycleListeners = new CopyOnWriteArrayList<>(); + protected LocalMetaArea localMetaArea; + /** + * Helper class for performing validation of resource names and locations. + */ + protected final LocationValidator locationValidator = new LocationValidator(this); + protected MarkerManager markerManager; + /** + * The currently installed Move/Delete hook. + */ + protected IMoveDeleteHook moveDeleteHook = null; + protected NatureManager natureManager; + protected FilterTypeManager filterManager; + protected long nextMarkerId = 0; + protected long nextNodeId = 1; + + protected NotificationManager notificationManager; + protected boolean openFlag = false; + protected ElementTree operationTree; // tree at the start of the current operation + protected PathVariableManager pathVariableManager; + protected IPropertyManager propertyManager; + + protected RefreshManager refreshManager; + + /** + * Scheduling rule factory. This field is null if the factory has not been used + * yet. The accessor method should be used rather than accessing this field + * directly. + */ + private IResourceRuleFactory ruleFactory; + + protected SaveManager saveManager; + /** + * File modification validation. If it is true and validator is null, we try/initialize + * validator first time through. If false, there is no validator. + */ + protected boolean shouldValidate = true; + + /** + * Job that performs periodic string pool canonicalization. + */ + private StringPoolJob stringPoolJob; + + /** + * The synchronizer + */ + protected Synchronizer synchronizer; + + /** + * The currently installed team hook. + */ + protected TeamHook teamHook = null; + + /** + * The workspace tree. The tree is an in-memory representation + * of the resources that make up the workspace. The tree caches + * the structure and state of files and directories on disk (their existence + * and last modified times). When external parties make changes to + * the files on disk, this representation becomes out of sync. A local refresh + * reconciles the state of the files on disk with this tree (@link {@link IResource#refreshLocal(int, IProgressMonitor)}). + * The tree is also used to store metadata associated with resources in + * the workspace (markers, properties, etc). + * + * While the ElementTree data structure can handle both concurrent + * reads and concurrent writes, write access to the tree is governed + * by {@link WorkManager}. + */ + protected volatile ElementTree tree; + + /** + * This field is used to control access to the workspace tree during + * resource change notifications. It tracks which thread, if any, is + * in the middle of a resource change notification. This is used to cause + * attempts to modify the workspace during notifications to fail. + */ + protected volatile Thread treeLocked = null; + + /** + * The currently installed file modification validator. + */ + protected IFileModificationValidator validator = null; + + /** + * Data structure for holding the multi-part outcome of + * IWorkspace.computeProjectBuildConfigOrder. + *

          + * This class is not intended to be instantiated by clients. + *

          + * + * @see Workspace#computeProjectBuildConfigOrder(IBuildConfiguration[]) + * @since 3.7 + */ + public static final class ProjectBuildConfigOrder { + /** + * Creates an instance with the given values. + *

          + * This class is not intended to be instantiated by clients. + *

          + * + * @param buildConfigurations initial value of buildConfigurations field + * @param hasCycles initial value of hasCycles field + * @param knots initial value of knots field + */ + public ProjectBuildConfigOrder(IBuildConfiguration[] buildConfigurations, boolean hasCycles, IBuildConfiguration[][] knots) { + this.buildConfigurations = buildConfigurations; + this.hasCycles = hasCycles; + this.knots = knots; + } + + /** + * A list of project buildConfigs ordered so as to honor the build configuration reference + * relationships between these project buildConfigs wherever possible. The elements + * are a subset of the ones passed as the buildConfigurations + * parameter to IWorkspace.computeProjectOrder, where + * inaccessible (closed or non-existent) projects have been omitted. + */ + public IBuildConfiguration[] buildConfigurations; + /** + * Indicates whether any of the accessible project buildConfigs in + * buildConfigurations are involved in non-trivial cycles. + * true if the reference graph contains at least + * one cycle involving two or more of the project buildConfigs in + * buildConfigurations, and false if none of the + * project buildConfigs in buildConfigurations are involved in cycles. + */ + 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 accessible project buildConfigs from buildConfigurations that are + * involved in a cycle of mutually dependent references. + */ + public IBuildConfiguration[][] knots; + } + + // Comparator used to provide a stable ordering of project buildConfigs + private static class BuildConfigurationComparator implements Comparator { + public BuildConfigurationComparator() { + } + + @Override + public int compare(IBuildConfiguration px, IBuildConfiguration py) { + int cmp = py.getProject().getName().compareTo(px.getProject().getName()); + if (cmp == 0) + cmp = py.getName().compareTo(px.getName()); + return cmp; + } + } + + /** + * Deletes all the files and directories from the given root down (inclusive). + * Returns false if we could not delete some file or an exception occurred + * at any point in the deletion. + * Even if an exception occurs, a best effort is made to continue deleting. + */ + public static boolean clear(java.io.File root) { + IFileStore fileStore = EFS.getLocalFileSystem().fromLocalFile(root); + try { + fileStore.delete(EFS.NONE, new NullProgressMonitor()); + } catch (CoreException e) { + return false; + } + return true; + } + + public static WorkspaceDescription defaultWorkspaceDescription() { + return new WorkspaceDescription("Workspace"); //$NON-NLS-1$ + } + + /** + * Returns true if the object at the specified position has any + * other copy in the given array. + */ + private static boolean isDuplicate(Object[] array, int position) { + if (array == null || position >= array.length) + return false; + for (int j = position - 1; j >= 0; j--) + if (array[j].equals(array[position])) + return true; + return false; + } + + public Workspace() { + super(); + localMetaArea = new LocalMetaArea(); + tree = new ElementTree(); + /* tree should only be modified during operations */ + tree.immutable(); + treeLocked = Thread.currentThread(); + tree.setTreeData(newElement(IResource.ROOT)); + } + + /** + * Indicates that a build is about to occur. Broadcasts the necessary + * deltas before the build starts. Note that this will cause POST_BUILD + * to be automatically done at the end of the operation in which + * the build occurs. + */ + protected void aboutToBuild(Object source, int trigger) { + //fire a POST_CHANGE first to ensure everyone is up to date before firing PRE_BUILD + broadcastPostChange(); + broadcastBuildEvent(source, IResourceChangeEvent.PRE_BUILD, trigger); + } + + /** + * Adds a listener for internal workspace lifecycle events. There is no way to + * remove lifecycle listeners. + */ + public void addLifecycleListener(ILifecycleListener listener) { + lifecycleListeners.addIfAbsent(listener); + } + + @Override + public void addResourceChangeListener(IResourceChangeListener listener) { + notificationManager.addListener(listener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE); + } + + @Override + public void addResourceChangeListener(IResourceChangeListener listener, int eventMask) { + notificationManager.addListener(listener, eventMask); + } + + /** + * @deprecated Use {@link #addSaveParticipant(String, ISaveParticipant)} instead + */ + @Deprecated + @Override + public ISavedState addSaveParticipant(Plugin plugin, ISaveParticipant participant) throws CoreException { + Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$ + Assert.isNotNull(participant, "Participant must not be null"); //$NON-NLS-1$ + return saveManager.addParticipant(plugin.getBundle().getSymbolicName(), participant); + } + + @Override + public ISavedState addSaveParticipant(String pluginId, ISaveParticipant participant) throws CoreException { + Assert.isNotNull(pluginId, "Plugin id must not be null"); //$NON-NLS-1$ + Assert.isNotNull(participant, "Participant must not be null"); //$NON-NLS-1$ + return saveManager.addParticipant(pluginId, participant); + } + + public void beginOperation(boolean createNewTree) throws CoreException { + WorkManager workManager = getWorkManager(); + workManager.incrementNestedOperations(); + if (!workManager.isBalanced()) + Assert.isTrue(false, "Operation was not prepared."); //$NON-NLS-1$ + if (workManager.getPreparedOperationDepth() > 1) { + if (createNewTree && tree.isImmutable()) + newWorkingTree(); + return; + } + // stash the current tree as the basis for this operation. + operationTree = tree; + if (createNewTree && tree.isImmutable()) + newWorkingTree(); + } + + public void broadcastBuildEvent(Object source, int type, int buildTrigger) { + ResourceChangeEvent event = new ResourceChangeEvent(source, type, buildTrigger, null); + notificationManager.broadcastChanges(tree, event, false); + } + + /** + * Broadcasts an internal workspace lifecycle event to interested + * internal listeners. + */ + protected void broadcastEvent(LifecycleEvent event) throws CoreException { + for (ILifecycleListener listener : lifecycleListeners) + listener.handleEvent(event); + } + + public void broadcastPostChange() { + ResourceChangeEvent event = new ResourceChangeEvent(this, IResourceChangeEvent.POST_CHANGE, 0, null); + notificationManager.broadcastChanges(tree, event, true); + } + + /** + * Add all IBuildConfigurations reachable from config to the configs collection. + * @param configs collection of configurations to extend + * @param config config to find reachable configurations to. + */ + private void recursivelyAddBuildConfigs(Collection/**/ configs, IBuildConfiguration config) { + try { + IBuildConfiguration[] referenced = config.getProject().getReferencedBuildConfigs(config.getName(), false); + for (int i = 0; i < referenced.length; i++) { + if (configs.contains(referenced[i])) + continue; + configs.add(referenced[i]); + recursivelyAddBuildConfigs(configs, referenced[i]); + } + } catch (CoreException e) { + // Not possible, we've checked that the project + configuration are accessible. + Assert.isTrue(false); + } + } + + @Override + public void build(int trigger, IProgressMonitor monitor) throws CoreException { + buildInternal(EMPTY_BUILD_CONFIG_ARRAY, trigger, true, monitor); + } + + @Override + public void build(IBuildConfiguration[] configs, int trigger, boolean buildReferences, IProgressMonitor monitor) throws CoreException { + if (configs.length == 0) + return; + buildInternal(configs, trigger, buildReferences, monitor); + } + + /** + * Build the passed in configurations or the whole workspace. + * @param configs to build or EMPTY_BUILD_CONFIG_ARRAY for the whole workspace + * @param trigger build trigger + * @param buildReferences transitively build referenced build configurations + */ + private void buildInternal(IBuildConfiguration[] configs, int trigger, boolean buildReferences, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + // Bug 343256 use a relaxed scheduling rule if the config we're building uses a relaxed rule. + // Otherwise fall-back to WR. + boolean relaxed = false; + if (Job.getJobManager().currentRule() == null && configs.length > 0) { + relaxed = true; + for (IBuildConfiguration config : configs) { + ISchedulingRule requested = getBuildManager().getRule(config, trigger, null, null); + if (requested != null && requested.contains(getRoot())) { + relaxed = false; + break; + } + } + } + + // PRE + POST_BUILD, and the build itself are allowed to modify resources, so require the current thread's scheduling rule + // to either contain the WR or be null. Therefore, if not null, ensure it contains the WR rule... + final ISchedulingRule buildRule = getRuleFactory().buildRule(); + final ISchedulingRule rule = relaxed ? null : buildRule; + try { + monitor.beginTask("", Policy.opWork); //$NON-NLS-1$ + try { + try { + // Must run the PRE_BUILD with the WRule held before acquiring WS lock + // Can remove this if we run notifications without the WS lock held: bug 249951 + prepareOperation(rule == null ? buildRule : rule, monitor); + beginOperation(true); + aboutToBuild(this, trigger); + } finally { + if (rule == null) { + endOperation(buildRule, false, monitor); + prepareOperation(rule, monitor); + beginOperation(false); + } + } + IStatus result; + try { + + // Calculate the build-order having called the pre-build notification (which may change build order) + // If configs == EMPTY_BUILD_CONFIG_ARRAY => This is a full workspace build. + IBuildConfiguration[] requestedConfigs = configs; + if (configs == EMPTY_BUILD_CONFIG_ARRAY) { + if (trigger != IncrementalProjectBuilder.CLEAN_BUILD) + configs = getBuildOrder(); + else { + // clean all accessible configurations + List configArr = new ArrayList<>(); + IProject[] prjs = getRoot().getProjects(); + for (int i = 0; i < prjs.length; i++) + if (prjs[i].isAccessible()) + configArr.addAll(Arrays.asList(prjs[i].getBuildConfigs())); + configs = configArr.toArray(new IBuildConfiguration[configArr.size()]); + } + } else { + // Order the passed in build configurations + resolve references if requested + Set refsList = new HashSet<>(); + for (int i = 0; i < configs.length; i++) { + // Check project + build configuration are accessible. + if (!configs[i].getProject().isAccessible() || !configs[i].getProject().hasBuildConfig(configs[i].getName())) + continue; + refsList.add(configs[i]); + // Find transitive closure of referenced project buildConfigs + if (buildReferences) + recursivelyAddBuildConfigs(refsList, configs[i]); + } + + // Order the referenced project buildConfigs + ProjectBuildConfigOrder order = computeProjectBuildConfigOrder(refsList.toArray(new IBuildConfiguration[refsList.size()])); + configs = order.buildConfigurations; + } + + result = getBuildManager().build(configs, requestedConfigs, trigger, Policy.subMonitorFor(monitor, Policy.opWork)); + } finally { + // Run the POST_BUILD with the WRule held + if (rule == null) { + endOperation(rule, false, monitor); + prepareOperation(buildRule, monitor); + beginOperation(false); + } + //must fire POST_BUILD if PRE_BUILD has occurred + broadcastBuildEvent(this, IResourceChangeEvent.POST_BUILD, trigger); + } + if (!result.isOK()) + throw new ResourceException(result); + } finally { + //building may close the tree, but we are still inside an operation so open it + if (tree.isImmutable()) + newWorkingTree(); + // Rule will be the build-rule from the POST_BUILD refresh + endOperation(buildRule, false, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + /** + * Returns whether creating executable extensions is acceptable + * at this point in time. In particular, returns false + * when the system bundle is shutting down, which only occurs + * when the entire framework is exiting. + */ + private boolean canCreateExtensions() { + return Platform.getBundle("org.eclipse.osgi").getState() != Bundle.STOPPING; //$NON-NLS-1$ + } + + @Override + public void checkpoint(boolean build) { + try { + final ISchedulingRule rule = getWorkManager().getNotifyRule(); + try { + prepareOperation(rule, null); + beginOperation(true); + broadcastPostChange(); + } finally { + endOperation(rule, build, null); + } + } catch (CoreException e) { + Policy.log(e.getStatus().getSeverity(), e.getMessage(), e); + } + } + + /** + * Closes this workspace; ignored if this workspace is not open. + * The state of this workspace is not saved before the workspace + * is shut down. + *

          + * If the workspace was saved immediately prior to closing, + * it will have the same set of projects + * (open or closed) when reopened for a subsequent session. + * Otherwise, closing a workspace may lose some or all of the + * changes made since the last save or snapshot. + *

          + *

          + * Note that session properties are discarded when a workspace is closed. + *

          + *

          + * 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 and cancellation are not desired + * @exception CoreException if the workspace could not be shutdown. + */ + public void close(IProgressMonitor monitor) throws CoreException { + //nothing to do if the workspace failed to open + if (!isOpen()) + return; + try { + String msg = Messages.resources_closing_0; + SubMonitor subMonitor = SubMonitor.convert(monitor, msg, 20); + subMonitor.subTask(msg); + //this operation will never end because the world is going away + try { + stringPoolJob.cancel(); + //shutdown save manager now so a last snapshot can be taken before we close + //note: you can't call #save() from within a nested operation + saveManager.shutdown(null); + saveManager.reportSnapshotRequestor(); + prepareOperation(getRoot(), subMonitor.newChild(1)); + //shutdown notification first to avoid calling third parties during shutdown + notificationManager.shutdown(null); + beginOperation(true); + IProject[] projects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + subMonitor.setWorkRemaining(projects.length + 2); + for (int i = 0; i < projects.length; i++) { + //notify managers of closing so they can cleanup + broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, projects[i])); + subMonitor.worked(1); + } + //empty the workspace tree so we leave in a clean state + deleteResource(getRoot()); + openFlag = false; + // endOperation not needed here + } finally { + // Shutdown needs to be executed regardless of failures + shutdown(subMonitor.newChild(2, SubMonitor.SUPPRESS_SUBTASK)); + } + } finally { + //release the scheduling rule to be a good job citizen + Job.getJobManager().endRule(getRoot()); + } + } + + /** + * Computes the global total ordering of all open projects in the + * workspace based on project references. If an existing and open project P + * references another existing and open project Q also included in the list, + * then Q should come before P in the resulting ordering. Closed and non- + * existent projects are ignored, and will not appear in the result. References + * to non-existent or closed projects are also ignored, as are any self- + * references. + *

          + * When there are choices, the choice is made in a reasonably stable way. For + * example, given an arbitrary choice between two projects, the one with the + * lower collating project name is usually selected. + *

          + *

          + * When the project reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references P1, + * P4 references P3, and P2 and P3 reference each other, then exactly one of the + * relationships between P2 and P3 will have to be ignored. The outcome will be + * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains + * complete details of any cycles present. + *

          + * + * @return result describing the global project order + * @since 2.1 + */ + private VertexOrder computeFullProjectOrder() { + // determine the full set of accessible projects in the workspace + // order the set in descending alphabetical order of project name + SortedSet allAccessibleProjects = new TreeSet<>(new Comparator() { + @Override + public int compare(IProject px, IProject py) { + return py.getName().compareTo(px.getName()); + } + }); + IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + // List edges + List edges = new ArrayList<>(allProjects.length); + for (int i = 0; i < allProjects.length; i++) { + Project project = (Project) allProjects[i]; + // ignore projects that are not accessible + if (!project.isAccessible()) + continue; + ProjectDescription desc = project.internalGetDescription(); + if (desc == null) + continue; + //obtain both static and dynamic project references + IProject[] refs = desc.getAllReferences(false); + allAccessibleProjects.add(project); + for (int j = 0; j < refs.length; j++) { + IProject ref = refs[j]; + // ignore self references and references to projects that are not accessible + if (ref.isAccessible() && !ref.equals(project)) + edges.add(new IProject[] {project, ref}); + } + } + return ComputeProjectOrder.computeVertexOrder(allAccessibleProjects, edges); + } + + /** + * Computes the global total ordering of all open projects' active buildConfigs in the + * workspace based on build configuration references. If an existing and open project's build config P + * references another existing and open project's build config Q, then Q should come before P + * in the resulting ordering. If a build config references a non-active build config it is + * added to the resulting ordered list. Closed and non-existent projects/buildConfigs are + * ignored, and will not appear in the result. References to non-existent or closed + * projects/buildConfigs are also ignored, as are any self-references. + *

          + * When there are choices, the choice is made in a reasonably stable way. For + * example, given an arbitrary choice between two project buildConfigs, the one with the + * lower collating project name and build config name will appear earlier in the list. + *

          + *

          + * When the build configuration reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references P1, + * P4 references P3, and P2 and P3 reference each other, then exactly one of the + * relationships between P2 and P3 will have to be ignored. The outcome will be + * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains + * complete details of any cycles present. + *

          + * + * @return result describing the global active build configuration order + */ + private VertexOrder computeActiveBuildConfigOrder() { + // Determine the full set of accessible active project buildConfigs in the workspace, + // and all the accessible project buildConfigs that they reference. This forms a set + // of all the project buildConfigs that will be returned. + // Order the set in descending alphabetical order of project name then build config name, + // as a secondary sort applied after sorting based on references, to achieve a stable + // ordering. + SortedSet allAccessibleBuildConfigs = new TreeSet<>(new BuildConfigurationComparator()); + + // For each project's active build config, perform a depth first search in the reference graph + // rooted at that build config. + // This generates the required subset of the reference graph that is required to order all + // the dependencies of the active project buildConfigs. + IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List edges = new ArrayList<>(allProjects.length); + + for (int i = 0; i < allProjects.length; i++) { + Project project = (Project) allProjects[i]; + // Ignore projects that are not accessible + if (!project.isAccessible()) + continue; + + // If the active build configuration hasn't already been explored + // perform a depth first search rooted at it + if (!allAccessibleBuildConfigs.contains(project.internalGetActiveBuildConfig())) { + allAccessibleBuildConfigs.add(project.internalGetActiveBuildConfig()); + Stack stack = new Stack<>(); + stack.push(project.internalGetActiveBuildConfig()); + + while (!stack.isEmpty()) { + IBuildConfiguration buildConfiguration = stack.pop(); + + // Add all referenced buildConfigs from the current configuration + // (it is guaranteed to be accessible as it was pushed onto the stack) + Project subProject = (Project) buildConfiguration.getProject(); + IBuildConfiguration[] refs = subProject.internalGetReferencedBuildConfigs(buildConfiguration.getName(), false); + for (int j = 0; j < refs.length; j++) { + IBuildConfiguration ref = refs[j]; + + // Ignore self references and references to projects that are not accessible + if (ref.equals(buildConfiguration)) + continue; + + // Add the referenced accessible configuration + edges.add(new IBuildConfiguration[] {buildConfiguration, ref}); + + // If we have already explored the referenced configuration, don't explore it again + if (allAccessibleBuildConfigs.contains(ref)) + continue; + + allAccessibleBuildConfigs.add(ref); + + // Push the referenced configuration onto the stack so that it is explored by the depth first search + stack.push(ref); + } + } + } + } + return ComputeProjectOrder.computeVertexOrder(allAccessibleBuildConfigs, edges); + } + + /** + * Computes the global total ordering of all project buildConfigs in the workspace based + * on build config references. If an existing and open build config P + * references another existing and open project build config Q, then Q should come before P + * in the resulting ordering. Closed and non-existent projects/buildConfigs are + * ignored, and will not appear in the result. References to non-existent or closed + * projects/buildConfigs are also ignored, as are any self-references. + *

          + * When there are choices, the choice is made in a reasonably stable way. For + * example, given an arbitrary choice between two project buildConfigs, the one with the + * lower collating project name and build config name will appear earlier in the list. + *

          + *

          + * When the build config reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references P1, + * P4 references P3, and P2 and P3 reference each other, then exactly one of the + * relationships between P2 and P3 will have to be ignored. The outcome will be + * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains + * complete details of any cycles present. + *

          + * + * @return result describing the global project build configuration order + */ + private VertexOrder computeFullBuildConfigOrder() { + // Compute the order for all accessible project buildConfigs + SortedSet allAccessibleBuildConfigurations = new TreeSet<>(new BuildConfigurationComparator()); + + IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List edges = new ArrayList<>(allProjects.length); + + for (int i = 0; i < allProjects.length; i++) { + Project project = (Project) allProjects[i]; + // Ignore projects that are not accessible + if (!project.isAccessible()) + continue; + + IBuildConfiguration[] configs = project.internalGetBuildConfigs(false); + for (int j = 0; j < configs.length; j++) { + IBuildConfiguration config = configs[j]; + allAccessibleBuildConfigurations.add(config); + IBuildConfiguration[] refs = project.internalGetReferencedBuildConfigs(config.getName(), false); + for (int k = 0; k < refs.length; k++) { + IBuildConfiguration ref = refs[k]; + + // Ignore self references + if (ref.equals(config)) + continue; + + // Add the reference to the set of reachable configs + add an edge + allAccessibleBuildConfigurations.add(ref); + edges.add(new IBuildConfiguration[] {config, ref}); + } + } + } + return ComputeProjectOrder.computeVertexOrder(allAccessibleBuildConfigurations, edges); + } + + private static ProjectOrder vertexOrderToProjectOrder(VertexOrder order) { + IProject[] projects = new IProject[order.vertexes.length]; + System.arraycopy(order.vertexes, 0, projects, 0, order.vertexes.length); + IProject[][] knots = new IProject[order.knots.length][]; + for (int i = 0; i < order.knots.length; i++) { + knots[i] = new IProject[order.knots[i].length]; + System.arraycopy(order.knots[i], 0, knots[i], 0, order.knots[i].length); + } + return new ProjectOrder(projects, order.hasCycles, knots); + } + + private static ProjectBuildConfigOrder vertexOrderToProjectBuildConfigOrder(VertexOrder order) { + IBuildConfiguration[] buildConfigs = new IBuildConfiguration[order.vertexes.length]; + System.arraycopy(order.vertexes, 0, buildConfigs, 0, order.vertexes.length); + IBuildConfiguration[][] knots = new IBuildConfiguration[order.knots.length][]; + for (int i = 0; i < order.knots.length; i++) { + knots[i] = new IBuildConfiguration[order.knots[i].length]; + System.arraycopy(order.knots[i], 0, knots[i], 0, order.knots[i].length); + } + return new ProjectBuildConfigOrder(buildConfigs, order.hasCycles, knots); + } + + @Deprecated + @Override + public IProject[][] computePrerequisiteOrder(IProject[] targets) { + return computePrerequisiteOrder1(targets); + } + + /* + * Compatible reimplementation of + * IWorkspace.computePrerequisiteOrder using + * IWorkspace.computeProjectOrder. + * + * @since 2.1 + */ + private IProject[][] computePrerequisiteOrder1(IProject[] projects) { + IWorkspace.ProjectOrder r = computeProjectOrder(projects); + if (!r.hasCycles) { + return new IProject[][] {r.projects, new IProject[0]}; + } + // when there are cycles, we need to remove all knotted projects from + // r.projects to form result[0] and merge all knots to form result[1] + // Set bad + Set bad = new HashSet<>(); + // Set bad + Set keepers = new HashSet<>(Arrays.asList(r.projects)); + for (int i = 0; i < r.knots.length; i++) { + IProject[] knot = r.knots[i]; + for (int j = 0; j < knot.length; j++) { + IProject project = knot[j]; + // keep only selected projects in knot + if (keepers.contains(project)) { + bad.add(project); + } + } + } + IProject[] result2 = new IProject[bad.size()]; + bad.toArray(result2); + // List p + List p = new LinkedList<>(); + p.addAll(Arrays.asList(r.projects)); + for (Iterator it = p.listIterator(); it.hasNext();) { + IProject project = it.next(); + if (bad.contains(project)) { + // remove knotted projects from the main answer + it.remove(); + } + } + IProject[] result1 = new IProject[p.size()]; + p.toArray(result1); + return new IProject[][] {result1, result2}; + } + + @Override + public ProjectOrder computeProjectOrder(IProject[] projects) { + // Compute the full project order for all accessible projects + VertexOrder fullProjectOrder = computeFullProjectOrder(); + + // Create a filter to remove all projects that are not in the list asked for + final Set projectSet = new HashSet<>(projects.length); + projectSet.addAll(Arrays.asList(projects)); + VertexFilter filter = new VertexFilter() { + @Override + public boolean matches(Object vertex) { + return !projectSet.contains(vertex); + } + }; + + // Filter the order and return it + return vertexOrderToProjectOrder(ComputeProjectOrder.filterVertexOrder(fullProjectOrder, filter)); + } + + /** + * Computes a total ordering of the given projects buildConfigs based on both static and + * dynamic project references. If an existing and open project's build configuratioin P references + * another existing and open project's configuration Q also included in the list, then Q + * should come before P in the resulting ordering. Closed and non-existent + * projects are ignored, and will not appear in the result. References to + * non-existent or closed projects/buildConfigs are also ignored, as are any + * self-references. The total ordering is always consistent with the global + * total ordering of all open projects' buildConfigs in the workspace. + *

          + * When there are choices, the choice is made in a reasonably stable way. + * For example, given an arbitrary choice between two project buildConfigs, the one with + * the lower collating configuration name is usually selected. + *

          + *

          + * When the project reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references + * P1, P4 references P3, and P2 and P3 reference each other, then exactly + * one of the relationships between P2 and P3 will have to be ignored. The + * outcome will be either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result + * also contains complete details of any cycles present. + *

          + *

          + * This method is time-consuming and should not be called unnecessarily. + * There are a very limited set of changes to a workspace that could affect + * the outcome: creating, renaming, or deleting a project; opening or + * closing a project; deleting a build configuration; adding or removing a build configuration reference. + *

          + * + * @param buildConfigs the build configurations to order + * @return result describing the build configuration order + * @since 3.7 + */ + public ProjectBuildConfigOrder computeProjectBuildConfigOrder(IBuildConfiguration[] buildConfigs) { + // Compute the full project order for all accessible projects + VertexOrder fullBuildConfigOrder = computeFullBuildConfigOrder(); + + // Create a filter to remove all project buildConfigs that are not in the list asked for + final Set projectConfigSet = new HashSet<>(buildConfigs.length); + projectConfigSet.addAll(Arrays.asList(buildConfigs)); + VertexFilter filter = new VertexFilter() { + @Override + public boolean matches(Object vertex) { + return !projectConfigSet.contains(vertex); + } + }; + + // Filter the order and return it + return vertexOrderToProjectBuildConfigOrder(ComputeProjectOrder.filterVertexOrder(fullBuildConfigOrder, filter)); + } + + @Override + public IStatus copy(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + return copy(resources, destination, updateFlags, monitor); + } + + @Override + public IStatus copy(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + Assert.isLegal(resources != null); + int opWork = Math.max(resources.length, 1); + int totalWork = Policy.totalWork * opWork / Policy.opWork; + String message = Messages.resources_copying_0; + monitor.beginTask(message, totalWork); + if (resources.length == 0) + return Status.OK_STATUS; + // to avoid concurrent changes to this array + resources = resources.clone(); + IPath parentPath = null; + message = Messages.resources_copyProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + try { + prepareOperation(getRoot(), monitor); + beginOperation(true); + for (int i = 0; i < resources.length; i++) { + Policy.checkCanceled(monitor); + IResource resource = resources[i]; + if (resource == null || isDuplicate(resources, i)) { + monitor.worked(1); + continue; + } + // test siblings + if (parentPath == null) + parentPath = resource.getFullPath().removeLastSegments(1); + if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) { + // test copy requirements + try { + IPath destinationPath = destination.append(resource.getName()); + IStatus requirements = ((Resource) resource).checkCopyRequirements(destinationPath, resource.getType(), updateFlags); + if (requirements.isOK()) { + try { + resource.copy(destinationPath, updateFlags, Policy.subMonitorFor(monitor, 1)); + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + status.merge(requirements); + } + } catch (CoreException e) { + monitor.worked(1); + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + message = NLS.bind(Messages.resources_notChild, resources[i].getFullPath(), parentPath); + status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resources[i].getFullPath(), message)); + } + } + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } finally { + endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork)); + } + if (status.matches(IStatus.ERROR)) + throw new ResourceException(status); + return status.isOK() ? Status.OK_STATUS : (IStatus) status; + } finally { + monitor.done(); + } + } + + protected void copyTree(IResource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo) throws CoreException { + copyTree(source, destination, depth, updateFlags, keepSyncInfo, false, source.getType() == IResource.PROJECT); + } + + private void copyTree(IResource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo, boolean moveResources, boolean movingProject) throws CoreException { + + // retrieve the resource at the destination if there is one (phantoms included). + // if there isn't one, then create a new handle based on the type that we are + // trying to copy + IResource destinationResource = getRoot().findMember(destination, true); + int destinationType; + if (destinationResource == null) { + if (source.getType() == IResource.FILE) + destinationType = IResource.FILE; + else if (destination.segmentCount() == 1) + destinationType = IResource.PROJECT; + else + destinationType = IResource.FOLDER; + destinationResource = newResource(destination, destinationType); + } else + destinationType = destinationResource.getType(); + + // create the resource at the destination + ResourceInfo sourceInfo = ((Resource) source).getResourceInfo(true, false); + if (destinationType != source.getType()) { + sourceInfo = (ResourceInfo) sourceInfo.clone(); + sourceInfo.setType(destinationType); + } + ResourceInfo newInfo = createResource(destinationResource, sourceInfo, false, true, keepSyncInfo); + // get/set the node id from the source's resource info so we can later put it in the + // info for the destination resource. This will help us generate the proper deltas, + // indicating a move rather than a add/delete + newInfo.setNodeId(sourceInfo.getNodeId()); + + // preserve local sync info but not location info + newInfo.setFlags(newInfo.getFlags() | (sourceInfo.getFlags() & M_LOCAL_EXISTS)); + newInfo.setFileStoreRoot(null); + + // forget content-related caching flags + newInfo.clear(M_CONTENT_CACHE); + + // update link locations in project descriptions + if (source.isLinked()) { + LinkDescription linkDescription; + URI sourceLocationURI = transferVariableDefinition(source, destinationResource, source.getLocationURI()); + if (((updateFlags & IResource.SHALLOW) != 0) || ((Resource) source).isUnderVirtual()) { + //for shallow move the destination is a linked resource with the same location + newInfo.set(ICoreConstants.M_LINK); + linkDescription = new LinkDescription(destinationResource, sourceLocationURI); + } else { + //for deep move the destination is not a linked resource + newInfo.clear(ICoreConstants.M_LINK); + linkDescription = null; + } + if (moveResources && !movingProject) { + if (((Project) source.getProject()).internalGetDescription().setLinkLocation(source.getProjectRelativePath(), null)) + ((Project) source.getProject()).writeDescription(updateFlags); + } + Project project = (Project) destinationResource.getProject(); + project.internalGetDescription().setLinkLocation(destinationResource.getProjectRelativePath(), linkDescription); + project.writeDescription(updateFlags); + newInfo.setFileStoreRoot(null); + } + + // update filters in project descriptions + if (source.getProject().exists() && source instanceof Container && ((Container) source).hasFilters()) { + Project sourceProject = (Project) source.getProject(); + LinkedList originalDescriptions = sourceProject.internalGetDescription().getFilter(source.getProjectRelativePath()); + LinkedList filterDescriptions = FilterDescription.copy(originalDescriptions, destinationResource); + if (moveResources && !movingProject) { + if (((Project) source.getProject()).internalGetDescription().setFilters(source.getProjectRelativePath(), null)) + ((Project) source.getProject()).writeDescription(updateFlags); + } + Project project = (Project) destinationResource.getProject(); + project.internalGetDescription().setFilters(destinationResource.getProjectRelativePath(), filterDescriptions); + project.writeDescription(updateFlags); + } + + // do the recursion. if we have a file then it has no members so return. otherwise + // recursively call this method on the container's members if the depth tells us to + if (depth == IResource.DEPTH_ZERO || source.getType() == IResource.FILE) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.DEPTH_ZERO; + //copy .project file first if project is being copied, otherwise links won't be able to update description + boolean projectCopy = source.getType() == IResource.PROJECT && destinationType == IResource.PROJECT; + if (projectCopy) { + IResource dotProject = ((Project) source).findMember(IProjectDescription.DESCRIPTION_FILE_NAME); + if (dotProject != null) + copyTree(dotProject, destination.append(dotProject.getName()), depth, updateFlags, keepSyncInfo, moveResources, movingProject); + } + IResource[] children = ((IContainer) source).members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + for (int i = 0, imax = children.length; i < imax; i++) { + String childName = children[i].getName(); + if (!projectCopy || !childName.equals(IProjectDescription.DESCRIPTION_FILE_NAME)) { + IPath childPath = destination.append(childName); + copyTree(children[i], childPath, depth, updateFlags, keepSyncInfo, moveResources, movingProject); + } + } + } + + public URI transferVariableDefinition(IResource source, IResource dest, URI sourceURI) throws CoreException { + IPath srcLoc = source.getLocation(); + IPath srcRawLoc = source.getRawLocation(); + if ((srcLoc != null) && (srcRawLoc != null) && !srcLoc.equals(srcRawLoc)) { + // the location is variable relative + if (!source.getProject().equals(dest.getProject())) { + String variable = srcRawLoc.segment(0); + variable = copyVariable(source, dest, variable); + IPath newLocation = Path.fromPortableString(variable).append(srcRawLoc.removeFirstSegments(1)); + sourceURI = toURI(newLocation); + } else { + sourceURI = toURI(srcRawLoc); + } + } + return sourceURI; + } + + URI toURI(IPath path) { + if (path.isAbsolute()) + return org.eclipse.core.filesystem.URIUtil.toURI(path); + try { + return new URI(null, null, path.toPortableString(), null); + } catch (URISyntaxException e) { + return org.eclipse.core.filesystem.URIUtil.toURI(path); + } + } + + String copyVariable(IResource source, IResource dest, String variable) throws CoreException { + IPathVariableManager destPathVariableManager = dest.getPathVariableManager(); + IPathVariableManager srcPathVariableManager = source.getPathVariableManager(); + + IPath srcValue = URIUtil.toPath(srcPathVariableManager.getURIValue(variable)); + if (srcValue == null) // if the variable doesn't exist, return another + // variable that doesn't exist either + return PathVariableUtil.getUniqueVariableName(variable, dest); + IPath resolvedSrcValue = URIUtil.toPath(srcPathVariableManager.resolveURI(URIUtil.toURI(srcValue))); + + boolean variableExisted = false; + // look if the exact same variable exists + if (destPathVariableManager.isDefined(variable)) { + variableExisted = true; + IPath destValue = URIUtil.toPath(destPathVariableManager.getURIValue(variable)); + if (destValue != null && URIUtil.toPath(destPathVariableManager.resolveURI(URIUtil.toURI(destValue))).equals(resolvedSrcValue)) + return variable; + } + // look if one variable in the destination project matches + String[] variables = destPathVariableManager.getPathVariableNames(); + for (int i = 0; i < variables.length; i++) { + if (!PathVariableUtil.isPreferred(variables[i])) + continue; + IPath resolveDestVariable = URIUtil.toPath(destPathVariableManager.resolveURI(destPathVariableManager.getURIValue(variables[i]))); + if (resolveDestVariable != null && resolveDestVariable.equals(resolvedSrcValue)) { + return variables[i]; + } + } + // if the variable doesn't exist in the dest project, or + // if the value is different than the source project, we have to create + // an equivalent. + String destVariable = PathVariableUtil.getUniqueVariableName(variable, dest); + + boolean shouldConvertToRelative = true; + if (!srcValue.equals(resolvedSrcValue) && !variableExisted) { + // the variable content contains references to more variables + + String[] referencedVariables = PathVariableUtil.splitVariableNames(srcValue.toPortableString()); + shouldConvertToRelative = false; + // If the variable value is of type ${PARENT-COUNT-VAR}, + // we can avoid generating an intermediate variable and convert it directly. + if (referencedVariables.length == 1) { + if (PathVariableUtil.isParentVariable(referencedVariables[0])) + shouldConvertToRelative = true; + } + + if (!shouldConvertToRelative) { + String[] segments = PathVariableUtil.splitVariablesAndContent(srcValue.toPortableString()); + StringBuffer result = new StringBuffer(); + for (int i = 0; i < segments.length; i++) { + String var = PathVariableUtil.extractVariable(segments[i]); + if (var.length() > 0) { + String copiedVariable = copyVariable(source, dest, var); + int index = segments[i].indexOf(var); + if (index != -1) { + result.append(segments[i].substring(0, index)); + result.append(copiedVariable); + int start = index + var.length(); + int end = segments[i].length(); + result.append(segments[i].substring(start, end)); + } + } else + result.append(segments[i]); + } + srcValue = Path.fromPortableString(result.toString()); + } + } + if (shouldConvertToRelative) { + IPath relativeSrcValue = PathVariableUtil.convertToPathRelativeMacro(destPathVariableManager, resolvedSrcValue, dest, true, null); + if (relativeSrcValue != null) + srcValue = relativeSrcValue; + } + destPathVariableManager.setURIValue(destVariable, URIUtil.toURI(srcValue)); + return destVariable; + } + + /** + * Returns the number of resources in a subtree of the resource tree. + * + * @param root The subtree to count resources for + * @param depth The depth of the subtree to count + * @param phantom If true, phantoms are included, otherwise they are ignored. + */ + public int countResources(IPath root, int depth, final boolean phantom) { + if (!tree.includes(root)) + return 0; + switch (depth) { + case IResource.DEPTH_ZERO : + return 1; + case IResource.DEPTH_ONE : + return 1 + tree.getChildCount(root); + case IResource.DEPTH_INFINITE : + final int[] count = new int[1]; + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree aTree, IPathRequestor requestor, Object elementContents) { + if (phantom || !((ResourceInfo) elementContents).isSet(M_PHANTOM)) + count[0]++; + return true; + } + }; + new ElementTreeIterator(tree, root).iterate(visitor); + return count[0]; + } + return 0; + } + + /* + * Creates the given resource in the tree and returns the new resource info object. + * If phantom is true, the created element is marked as a phantom. + * If there is already be an element in the tree for the given resource + * in the given state (i.e., phantom), a CoreException is thrown. + * If there is already a phantom in the tree and the phantom flag is false, + * the element is overwritten with the new element. (but the synchronization + * information is preserved) + */ + public ResourceInfo createResource(IResource resource, boolean phantom) throws CoreException { + return createResource(resource, null, phantom, false, false); + } + + /** + * Creates a resource, honoring update flags requesting that the resource + * be immediately made derived, hidden and/or team private + */ + public ResourceInfo createResource(IResource resource, int updateFlags) throws CoreException { + ResourceInfo info = createResource(resource, null, false, false, false); + if ((updateFlags & IResource.DERIVED) != 0) + info.set(M_DERIVED); + if ((updateFlags & IResource.TEAM_PRIVATE) != 0) + info.set(M_TEAM_PRIVATE_MEMBER); + if ((updateFlags & IResource.HIDDEN) != 0) + info.set(M_HIDDEN); + // if ((updateFlags & IResource.VIRTUAL) != 0) + // info.set(M_VIRTUAL); + return info; + } + + /* + * Creates the given resource in the tree and returns the new resource info object. + * If phantom is true, the created element is marked as a phantom. + * If there is already be an element in the tree for the given resource + * in the given state (i.e., phantom), a CoreException is thrown. + * If there is already a phantom in the tree and the phantom flag is false, + * the element is overwritten with the new element. (but the synchronization + * information is preserved) If the specified resource info is null, then create + * a new one. + * + * If keepSyncInfo is set to be true, the sync info in the given ResourceInfo is NOT + * cleared before being created and thus any sync info already existing at that namespace + * (as indicated by an already existing phantom resource) will be lost. + */ + public ResourceInfo createResource(IResource resource, ResourceInfo info, boolean phantom, boolean overwrite, boolean keepSyncInfo) throws CoreException { + info = info == null ? newElement(resource.getType()) : (ResourceInfo) info.clone(); + ResourceInfo original = getResourceInfo(resource.getFullPath(), true, false); + if (phantom) { + info.set(M_PHANTOM); + info.clearModificationStamp(); + } + // if nothing existed at the destination then just create the resource in the tree + if (original == null) { + // we got here from a copy/move. we don't want to copy over any sync info + // from the source so clear it. + if (!keepSyncInfo) + info.setSyncInfo(null); + tree.createElement(resource.getFullPath(), info); + } else { + // if overwrite==true then slam the new info into the tree even if one existed before + if (overwrite || (!phantom && original.isSet(M_PHANTOM))) { + // copy over the sync info and flags from the old resource info + // since we are replacing a phantom with a real resource + // DO NOT set the sync info dirty flag because we want to + // preserve the old sync info so its not dirty + // XXX: must copy over the generic sync info from the old info to the new + // XXX: do we really need to clone the sync info here? + if (!keepSyncInfo) + info.setSyncInfo(original.getSyncInfo(true)); + // mark the markers bit as dirty so we snapshot an empty marker set for + // the new resource + info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY); + tree.setElementData(resource.getFullPath(), info); + } else { + String message = NLS.bind(Messages.resources_mustNotExist, resource.getFullPath()); + throw new ResourceException(IResourceStatus.RESOURCE_EXISTS, resource.getFullPath(), message, null); + } + } + return info; + } + + @Override + public IStatus delete(IResource[] resources, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= IResource.KEEP_HISTORY; + return delete(resources, updateFlags, monitor); + } + + @Override + public IStatus delete(IResource[] resources, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + int opWork = Math.max(resources.length, 1); + int totalWork = Policy.totalWork * opWork / Policy.opWork; + String message = Messages.resources_deleting_0; + monitor.beginTask(message, totalWork); + message = Messages.resources_deleteProblem; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + if (resources.length == 0) + return result; + resources = resources.clone(); // to avoid concurrent changes to this array + try { + prepareOperation(getRoot(), monitor); + beginOperation(true); + for (int i = 0; i < resources.length; i++) { + Policy.checkCanceled(monitor); + Resource resource = (Resource) resources[i]; + if (resource == null) { + monitor.worked(1); + continue; + } + try { + resource.delete(updateFlags, Policy.subMonitorFor(monitor, 1)); + } catch (CoreException e) { + // Don't really care about the exception unless the resource is still around. + ResourceInfo info = resource.getResourceInfo(false, false); + if (resource.exists(resource.getFlags(info), false)) { + message = NLS.bind(Messages.resources_couldnotDelete, resource.getFullPath()); + result.merge(new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, resource.getFullPath(), message)); + result.merge(e.getStatus()); + } + } + } + if (result.matches(IStatus.ERROR)) + throw new ResourceException(result); + return result; + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } finally { + endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void deleteMarkers(IMarker[] markers) throws CoreException { + Assert.isNotNull(markers); + if (markers.length == 0) + return; + // clone to avoid outside changes + markers = markers.clone(); + try { + prepareOperation(null, null); + beginOperation(true); + for (int i = 0; i < markers.length; ++i) + if (markers[i] != null && markers[i].getResource() != null) + markerManager.removeMarker(markers[i].getResource(), markers[i].getId()); + } finally { + endOperation(null, false, null); + } + } + + /** + * Delete the given resource from the current tree of the receiver. + * This method simply removes the resource from the tree. No cleanup or + * other management is done. Use IResource.delete for proper deletion. + * If the given resource is the root, all of its children (i.e., all projects) are + * deleted but the root is left. + */ + void deleteResource(IResource resource) { + IPath path = resource.getFullPath(); + if (path.equals(Path.ROOT)) { + IProject[] children = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < children.length; i++) + tree.deleteElement(children[i].getFullPath()); + } else + tree.deleteElement(path); + } + + /** + * End an operation (group of resource changes). + * Notify interested parties that resource changes have taken place. All + * registered resource change listeners are notified. If autobuilding is + * enabled, a build is run. + */ + public void endOperation(ISchedulingRule rule, boolean build, IProgressMonitor monitor) throws CoreException { + WorkManager workManager = getWorkManager(); + //don't do any end operation work if we failed to check in + if (workManager.checkInFailed(rule)) + return; + // This is done in a try finally to ensure that we always decrement the operation count + // and release the workspace lock. This must be done at the end because snapshot + // and "hasChanges" comparison have to happen without interference from other threads. + boolean hasTreeChanges = false; + boolean depthOne = false; + try { + workManager.setBuild(build); + // if we are not exiting a top level operation then just decrement the count and return + depthOne = workManager.getPreparedOperationDepth() == 1; + if (!(notificationManager.shouldNotify() || depthOne)) { + notificationManager.requestNotify(); + return; + } + // do the following in a try/finally to ensure that the operation tree is nulled at the end + // as we are completing a top level operation. + try { + notificationManager.beginNotify(); + // check for a programming error on using beginOperation/endOperation + Assert.isTrue(workManager.getPreparedOperationDepth() > 0, "Mismatched begin/endOperation"); //$NON-NLS-1$ + + // At this time we need to re-balance the nested operations. It is necessary because + // build() and snapshot() should not fail if they are called. + workManager.rebalanceNestedOperations(); + + //find out if any operation has potentially modified the tree + hasTreeChanges = workManager.shouldBuild(); + //double check if the tree has actually changed + if (hasTreeChanges) + hasTreeChanges = operationTree != null && ElementTree.hasChanges(tree, operationTree, ResourceComparator.getBuildComparator(), true); + broadcastPostChange(); + // Request a snapshot if we are sufficiently out of date. + saveManager.snapshotIfNeeded(hasTreeChanges); + } finally { + // make sure the tree is immutable if we are ending a top-level operation. + if (depthOne) { + tree.immutable(); + operationTree = null; + } else + newWorkingTree(); + } + } finally { + workManager.checkOut(rule); + } + if (depthOne) + buildManager.endTopLevel(hasTreeChanges); + } + + /** + * Flush the build order cache for the workspace. The buildOrder cache contains the total + * order of the build configurations in the workspace, including projects not mentioned in + * the workspace description. + */ + protected void flushBuildOrder() { + buildOrder = null; + } + + @Override + public void forgetSavedTree(String pluginId) { + saveManager.forgetSavedTree(pluginId); + } + + public AliasManager getAliasManager() { + return aliasManager; + } + + /** + * Returns this workspace's build manager + */ + public BuildManager getBuildManager() { + return buildManager; + } + + /** + * Returns the order in which open projects in this workspace will be built. + * The result returned is a list of project buildConfigs, that need to be built + * in order to successfully build the active config of every project in this + * workspace. + *

          + * The build configuration order is based on information specified in the workspace + * description. The project build configs are built in the order specified by + * IWorkspaceDescription.getBuildOrder; closed or non-existent + * projects are ignored and not included in the result. If any open projects are + * not specified in this order, they are appended to the end of the build order + * sorted by project name (to provide a stable ordering). + *

          + *

          + * If IWorkspaceDescription.getBuildOrder is non-null, the default + * build order is used (calculated based on references); again, only open projects' + * buildConfigs are included in the result. + *

          + *

          + * The returned value is cached in the buildOrder field. + *

          + * + * @return the list of currently open projects active buildConfigs (and the project buildConfigs + * they depend on) in the workspace in the order in which they would be built by IWorkspace.build. + * @see IWorkspace#build(int, IProgressMonitor) + * @see IWorkspaceDescription#getBuildOrder() + */ + public IBuildConfiguration[] getBuildOrder() { + // Return the build order cache. + if (buildOrder != null) + return buildOrder; + + // see if a particular build order is specified + String[] order = description.getBuildOrder(false); + if (order != null) { + LinkedHashSet configs = new LinkedHashSet<>(); + + // convert from project names to active project buildConfigs + // and eliminate non-existent and closed projects + for (int i = 0; i < order.length; i++) { + IProject project = getRoot().getProject(order[i]); + if (project.isAccessible()) + configs.add(((Project) project).internalGetActiveBuildConfig()); + } + + // Add projects not mentioned in the build order to the end, in a sensible reference order + configs.addAll(Arrays.asList(vertexOrderToProjectBuildConfigOrder(computeActiveBuildConfigOrder()).buildConfigurations)); + + // Update the cache - Java 5 volatile memory barrier semantics + IBuildConfiguration[] bo = new IBuildConfiguration[configs.size()]; + configs.toArray(bo); + this.buildOrder = bo; + } else + // use default project build order + // computed for all accessible projects in workspace + buildOrder = vertexOrderToProjectBuildConfigOrder(computeActiveBuildConfigOrder()).buildConfigurations; + + return buildOrder; + } + + public CharsetManager getCharsetManager() { + return charsetManager; + } + + public ContentDescriptionManager getContentDescriptionManager() { + return contentDescriptionManager; + } + + @Override + public Map getDanglingReferences() { + IProject[] projects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + Map result = new HashMap<>(projects.length); + for (int i = 0; i < projects.length; i++) { + Project project = (Project) projects[i]; + if (!project.isAccessible()) + continue; + IProject[] refs = project.internalGetDescription().getReferencedProjects(false); + List dangling = new ArrayList<>(refs.length); + for (int j = 0; j < refs.length; j++) + if (!refs[i].exists()) + dangling.add(refs[i]); + if (!dangling.isEmpty()) + result.put(projects[i], dangling.toArray(new IProject[dangling.size()])); + } + return result; + } + + @Override + public IWorkspaceDescription getDescription() { + WorkspaceDescription workingCopy = defaultWorkspaceDescription(); + description.copyTo(workingCopy); + return workingCopy; + } + + /** + * Returns the current element tree for this workspace + */ + public ElementTree getElementTree() { + return tree; + } + + public FileSystemResourceManager getFileSystemManager() { + return fileSystemManager; + } + + /** + * Returns the marker manager for this workspace + */ + public MarkerManager getMarkerManager() { + return markerManager; + } + + public LocalMetaArea getMetaArea() { + return localMetaArea; + } + + protected IMoveDeleteHook getMoveDeleteHook() { + if (moveDeleteHook == null) + initializeMoveDeleteHook(); + return moveDeleteHook; + } + + @Override + public IFilterMatcherDescriptor getFilterMatcherDescriptor(String filterMatcherId) { + return filterManager.getFilterDescriptor(filterMatcherId); + } + + @Override + public IFilterMatcherDescriptor[] getFilterMatcherDescriptors() { + return filterManager.getFilterDescriptors(); + } + + @Override + public IProjectNatureDescriptor getNatureDescriptor(String natureId) { + return natureManager.getNatureDescriptor(natureId); + } + + @Override + public IProjectNatureDescriptor[] getNatureDescriptors() { + return natureManager.getNatureDescriptors(); + } + + /** + * Returns the nature manager for this workspace. + */ + public NatureManager getNatureManager() { + return natureManager; + } + + public NotificationManager getNotificationManager() { + return notificationManager; + } + + @Override + public IPathVariableManager getPathVariableManager() { + return pathVariableManager; + } + + public IPropertyManager getPropertyManager() { + return propertyManager; + } + + /** + * Returns the refresh manager for this workspace + */ + public RefreshManager getRefreshManager() { + return refreshManager; + } + + /** + * Returns the resource info for the identified resource. + * null is returned if no such resource can be found. + * If the phantom flag is true, phantom resources are considered. + * If the mutable flag is true, the info is opened for change. + * + * This method DOES NOT throw an exception if the resource is not found. + */ + public ResourceInfo getResourceInfo(IPath path, boolean phantom, boolean mutable) { + try { + if (path.segmentCount() == 0) { + ResourceInfo info = (ResourceInfo) tree.getTreeData(); + Assert.isNotNull(info, "Tree root info must never be null"); //$NON-NLS-1$ + return info; + } + ResourceInfo result = null; + if (!tree.includes(path)) + return null; + if (mutable) + result = (ResourceInfo) tree.openElementData(path); + else + result = (ResourceInfo) tree.getElementData(path); + if (result != null && (!phantom && result.isSet(M_PHANTOM))) + return null; + return result; + } catch (IllegalArgumentException e) { + return null; + } + } + + @Override + public IWorkspaceRoot getRoot() { + return defaultRoot; + } + + @Override + public IResourceRuleFactory getRuleFactory() { + //note that the rule factory is created lazily because it + //requires loading the teamHook extension + if (ruleFactory == null) + ruleFactory = new Rules(this); + return ruleFactory; + } + + public SaveManager getSaveManager() { + return saveManager; + } + + @Override + public ISynchronizer getSynchronizer() { + return synchronizer; + } + + /** + * Returns the installed team hook. Never returns null. + */ + protected TeamHook getTeamHook() { + if (teamHook == null) + initializeTeamHook(); + return teamHook; + } + + /** + * We should not have direct references to this field. All references should go through + * this method. + */ + public WorkManager getWorkManager() throws CoreException { + if (_workManager == null) { + String message = Messages.resources_shutdown; + throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, null, message)); + } + return _workManager; + } + + /** + * A move/delete hook hasn't been initialized. Check the extension point and + * try to create a new hook if a user has one defined as an extension. Otherwise + * use the Core's implementation as the default. + */ + protected void initializeMoveDeleteHook() { + try { + if (!canCreateExtensions()) + return; + IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MOVE_DELETE_HOOK); + // no-one is plugged into the extension point so disable validation + if (configs == null || configs.length == 0) { + return; + } + // can only have one defined at a time. log a warning + if (configs.length > 1) { + //XXX: should provide a meaningful status code + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneHook, null); + Policy.log(status); + return; + } + // otherwise we have exactly one hook extension. Try to create a new instance + // from the user-specified class. + try { + IConfigurationElement config = configs[0]; + moveDeleteHook = (IMoveDeleteHook) config.createExecutableExtension("class"); //$NON-NLS-1$ + } catch (CoreException e) { + //ignore the failure if we are shutting down (expected since extension + //provider plugin has probably already shut down + if (canCreateExtensions()) { + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initHook, e); + Policy.log(status); + } + } + } finally { + // for now just use Core's implementation + if (moveDeleteHook == null) + moveDeleteHook = new MoveDeleteHook(); + } + } + + /** + * A team hook hasn't been initialized. Check the extension point and + * try to create a new hook if a user has one defined as an extension. + * Otherwise use the Core's implementation as the default. + */ + protected void initializeTeamHook() { + try { + if (!canCreateExtensions()) + return; + IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_TEAM_HOOK); + // no-one is plugged into the extension point so disable validation + if (configs == null || configs.length == 0) { + return; + } + // can only have one defined at a time. log a warning + if (configs.length > 1) { + //XXX: should provide a meaningful status code + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneTeamHook, null); + Policy.log(status); + return; + } + // otherwise we have exactly one hook extension. Try to create a new instance + // from the user-specified class. + try { + IConfigurationElement config = configs[0]; + teamHook = (TeamHook) config.createExecutableExtension("class"); //$NON-NLS-1$ + } catch (CoreException e) { + //ignore the failure if we are shutting down (expected since extension + //provider plugin has probably already shut down + if (canCreateExtensions()) { + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initTeamHook, e); + Policy.log(status); + } + } + } finally { + // default to use Core's implementation + //create anonymous subclass because TeamHook is abstract + if (teamHook == null) + teamHook = new TeamHook() { + // empty + }; + } + } + + /** + * A file modification validator hasn't been initialized. Check the extension point and + * try to create a new validator if a user has one defined as an extension. + */ + protected void initializeValidator() { + shouldValidate = false; + if (!canCreateExtensions()) + return; + IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_FILE_MODIFICATION_VALIDATOR); + // no-one is plugged into the extension point so disable validation + if (configs == null || configs.length == 0) { + return; + } + // can only have one defined at a time. log a warning, disable validation, but continue with + // the #setContents (e.g. don't throw an exception) + if (configs.length > 1) { + //XXX: should provide a meaningful status code + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneValidator, null); + Policy.log(status); + return; + } + // otherwise we have exactly one validator extension. Try to create a new instance + // from the user-specified class. + try { + IConfigurationElement config = configs[0]; + validator = (IFileModificationValidator) config.createExecutableExtension("class"); //$NON-NLS-1$ + shouldValidate = true; + } catch (CoreException e) { + //ignore the failure if we are shutting down (expected since extension + //provider plugin has probably already shut down + if (canCreateExtensions()) { + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initValidator, e); + Policy.log(status); + } + } + } + + public WorkspaceDescription internalGetDescription() { + return description; + } + + @Override + public boolean isAutoBuilding() { + return description.isAutoBuilding(); + } + + public boolean isOpen() { + return openFlag; + } + + @Override + public boolean isTreeLocked() { + return treeLocked == Thread.currentThread(); + } + + /** + * Link the given tree into the receiver's tree at the specified resource. + */ + protected void linkTrees(IPath path, ElementTree[] newTrees) { + tree = tree.mergeDeltaChain(path, newTrees); + } + + @Override + public IProjectDescription loadProjectDescription(InputStream stream) throws CoreException { + IProjectDescription result = null; + result = new ProjectDescriptionReader().read(new InputSource(stream)); + if (result == null) { + String message = NLS.bind(Messages.resources_errorReadProject, stream.toString()); + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message, null); + throw new ResourceException(status); + } + return result; + } + + @Override + public IProjectDescription loadProjectDescription(IPath path) throws CoreException { + IProjectDescription result = null; + IOException e = null; + try { + result = new ProjectDescriptionReader().read(path); + if (result != null) { + // check to see if we are using in the default area or not. use java.io.File for + // testing equality because it knows better w.r.t. drives and case sensitivity + IPath user = path.removeLastSegments(1); + IPath platform = getRoot().getLocation().append(result.getName()); + if (!user.toFile().equals(platform.toFile())) + result.setLocation(user); + } + } catch (IOException ex) { + e = ex; + } + if (result == null || e != null) { + String message = NLS.bind(Messages.resources_errorReadProject, path.toOSString()); + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message, e); + throw new ResourceException(status); + } + return result; + } + + @Override + public IStatus move(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= IResource.KEEP_HISTORY; + return move(resources, destination, updateFlags, monitor); + } + + @Override + public IStatus move(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + Assert.isLegal(resources != null); + int opWork = Math.max(resources.length, 1); + int totalWork = Policy.totalWork * opWork / Policy.opWork; + String message = Messages.resources_moving_0; + monitor.beginTask(message, totalWork); + if (resources.length == 0) + return Status.OK_STATUS; + resources = resources.clone(); // to avoid concurrent changes to this array + IPath parentPath = null; + message = Messages.resources_moveProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + try { + prepareOperation(getRoot(), monitor); + beginOperation(true); + for (int i = 0; i < resources.length; i++) { + Policy.checkCanceled(monitor); + Resource resource = (Resource) resources[i]; + if (resource == null || isDuplicate(resources, i)) { + monitor.worked(1); + continue; + } + // test siblings + if (parentPath == null) + parentPath = resource.getFullPath().removeLastSegments(1); + if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) { + // test move requirements + try { + IStatus requirements = resource.checkMoveRequirements(destination.append(resource.getName()), resource.getType(), updateFlags); + if (requirements.isOK()) { + try { + resource.move(destination.append(resource.getName()), updateFlags, Policy.subMonitorFor(monitor, 1)); + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + status.merge(requirements); + } + } catch (CoreException e) { + monitor.worked(1); + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + message = NLS.bind(Messages.resources_notChild, resource.getFullPath(), parentPath); + status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resource.getFullPath(), message)); + } + } + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } finally { + endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork)); + } + if (status.matches(IStatus.ERROR)) + throw new ResourceException(status); + return status.isOK() ? (IStatus) Status.OK_STATUS : (IStatus) status; + } finally { + monitor.done(); + } + } + + /** + * Moves this resource's subtree to the destination. This operation should only be + * used by move methods. Destination must be a valid destination for this resource. + * The keepSyncInfo boolean is used to indicated whether or not the sync info should + * be moved from the source to the destination. + */ + + /* package */ + void move(Resource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo) throws CoreException { + // overlay the tree at the destination path, preserving any important info + // in any already existing resource information + copyTree(source, destination, depth, updateFlags, keepSyncInfo, true, source.getType() == IResource.PROJECT); + source.fixupAfterMoveSource(); + } + + /** + * Create and return a new tree element of the given type. + */ + protected ResourceInfo newElement(int type) { + ResourceInfo result = null; + switch (type) { + case IResource.FILE : + case IResource.FOLDER : + result = new ResourceInfo(); + break; + case IResource.PROJECT : + result = new ProjectInfo(); + break; + case IResource.ROOT : + result = new RootInfo(); + break; + } + result.setNodeId(nextNodeId()); + updateModificationStamp(result); + result.setType(type); + return result; + } + + @Override + public IBuildConfiguration newBuildConfig(String projectName, String configName) { + return new BuildConfiguration(getRoot().getProject(projectName), configName); + } + + @Override + public IProjectDescription newProjectDescription(String projectName) { + IProjectDescription result = new ProjectDescription(); + result.setName(projectName); + return result; + } + + public Resource newResource(IPath path, int type) { + String message; + switch (type) { + case IResource.FOLDER : + if (path.segmentCount() < ICoreConstants.MINIMUM_FOLDER_SEGMENT_LENGTH) { + message = "Path must include project and resource name: " + path.toString(); //$NON-NLS-1$ + Assert.isLegal(false, message); + } + return new Folder(path.makeAbsolute(), this); + case IResource.FILE : + if (path.segmentCount() < ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH) { + message = "Path must include project and resource name: " + path.toString(); //$NON-NLS-1$ + Assert.isLegal(false, message); + } + return new File(path.makeAbsolute(), this); + case IResource.PROJECT : + return (Resource) getRoot().getProject(path.lastSegment()); + case IResource.ROOT : + return (Resource) getRoot(); + } + Assert.isLegal(false); + // will never get here because of assertion. + return null; + } + + /** + * Opens a new mutable element tree layer, thus allowing + * modifications to the tree. + */ + public ElementTree newWorkingTree() { + tree = tree.newEmptyDelta(); + return tree; + } + + /** + * Returns the next, previously unassigned, marker id. + */ + protected long nextMarkerId() { + return nextMarkerId++; + } + + protected long nextNodeId() { + return nextNodeId++; + } + + /** + * Opens this workspace using the data at its location in the local file system. + * This workspace must not be open. + * If the operation succeeds, the result will detail any serious + * (but non-fatal) problems encountered while opening the workspace. + * The status code will be OK if there were no problems. + * An exception is thrown if there are fatal problems opening the workspace, + * in which case the workspace is left closed. + *

          + * 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 and cancellation are not desired + * @return status with code OK if no problems; + * otherwise status describing any serious but non-fatal problems. + * + * @exception CoreException if the workspace could not be opened. + * Reasons include: + *
            + *
          • There is no valid workspace structure at the given location + * in the local file system.
          • + *
          • The workspace structure on disk appears to be hopelessly corrupt.
          • + *
          + * @see ResourcesPlugin#getWorkspace() + */ + public IStatus open(IProgressMonitor monitor) throws CoreException { + // This method is not inside an operation because it is the one responsible for + // creating the WorkManager object (who takes care of operations). + String message = Messages.resources_workspaceOpen; + Assert.isTrue(!isOpen(), message); + if (!getMetaArea().hasSavedWorkspace()) { + message = Messages.resources_readWorkspaceMeta; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, Platform.getLocation(), message, null); + } + description = new WorkspacePreferences(); + + // if we have an old description file, read it (getting rid of it) + WorkspaceDescription oldDescription = getMetaArea().readOldWorkspace(); + if (oldDescription != null) { + description.copyFrom(oldDescription); + ResourcesPlugin.getPlugin().savePluginPreferences(); + } + + // create root location + localMetaArea.locationFor(getRoot()).toFile().mkdirs(); + + IProgressMonitor nullMonitor = Policy.monitorFor(null); + startup(nullMonitor); + //restart the notification manager so it is initialized with the right tree + notificationManager.startup(null); + openFlag = true; + if (crashed || refreshRequested()) { + try { + refreshManager.refresh(getRoot()); + } catch (RuntimeException e) { + //don't fail entire open if refresh failed, just report as warning + return new ResourceStatus(IResourceStatus.INTERNAL_ERROR, Path.ROOT, Messages.resources_errorMultiRefresh, e); + } + } + //finally register a string pool participant + stringPoolJob = new StringPoolJob(); + stringPoolJob.addStringPoolParticipant(saveManager, getRoot()); + return Status.OK_STATUS; + } + + /** + * Called before checking the pre-conditions of an operation. Optionally supply + * a scheduling rule to determine when the operation is safe to run. If a scheduling + * rule is supplied, this method will block until it is safe to run. + * + * @param rule the scheduling rule that describes what this operation intends to modify. + */ + public void prepareOperation(ISchedulingRule rule, IProgressMonitor monitor) throws CoreException { + try { + //make sure autobuild is not running if it conflicts with this operation + ISchedulingRule buildRule = getRuleFactory().buildRule(); + if (rule != null && buildRule != null && (rule.isConflicting(buildRule) || buildRule.isConflicting(rule))) + buildManager.interrupt(); + } finally { + getWorkManager().checkIn(rule, monitor); + } + if (!isOpen()) { + String message = Messages.resources_workspaceClosed; + throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, null); + } + } + + protected boolean refreshRequested() { + String[] args = Platform.getCommandLineArgs(); + for (int i = 0; i < args.length; i++) + if (args[i].equalsIgnoreCase(REFRESH_ON_STARTUP)) + return true; + return false; + } + + @Override + public void removeResourceChangeListener(IResourceChangeListener listener) { + notificationManager.removeListener(listener); + } + + @Deprecated + @Override + public void removeSaveParticipant(Plugin plugin) { + Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$ + saveManager.removeParticipant(plugin.getBundle().getSymbolicName()); + } + + @Override + public void removeSaveParticipant(String pluginId) { + Assert.isNotNull(pluginId, "Plugin id must not be null"); //$NON-NLS-1$ + saveManager.removeParticipant(pluginId); + } + + @Override + public void run(ICoreRunnable action, IProgressMonitor monitor) throws CoreException { + run(action, defaultRoot, IWorkspace.AVOID_UPDATE, monitor); + } + + @Override + public void run(ICoreRunnable action, ISchedulingRule rule, int options, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + int depth = -1; + boolean avoidNotification = (options & IWorkspace.AVOID_UPDATE) != 0; + try { + prepareOperation(rule, monitor); + beginOperation(true); + if (avoidNotification) + avoidNotification = notificationManager.beginAvoidNotify(); + depth = getWorkManager().beginUnprotected(); + action.run(Policy.subMonitorFor(monitor, Policy.opWork)); + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } catch (CoreException e) { + if (e.getStatus().getSeverity() == IStatus.CANCEL) + getWorkManager().operationCanceled(); + throw e; + } finally { + if (avoidNotification) + notificationManager.endAvoidNotify(); + if (depth >= 0) + getWorkManager().endUnprotected(depth); + endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + @Override + public void run(IWorkspaceRunnable action, IProgressMonitor monitor) throws CoreException { + run((ICoreRunnable) action, defaultRoot, IWorkspace.AVOID_UPDATE, monitor); + } + + @Override + public void run(IWorkspaceRunnable action, ISchedulingRule rule, int options, IProgressMonitor monitor) throws CoreException { + run((ICoreRunnable) action, rule, options, monitor); + } + + @Override + public IStatus save(boolean full, IProgressMonitor monitor) throws CoreException { + return this.save(full, false, monitor); + } + + public IStatus save(boolean full, boolean keepConsistencyWhenCanceled, IProgressMonitor monitor) throws CoreException { + String message; + if (full) { + //according to spec it is illegal to start a full save inside another operation + if (getWorkManager().isLockAlreadyAcquired()) { + message = Messages.resources_saveOp; + throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, new IllegalStateException()); + } + return saveManager.save(ISaveContext.FULL_SAVE, keepConsistencyWhenCanceled, null, monitor); + } + // A snapshot was requested. Start an operation (if not already started) and + // signal that a snapshot should be done at the end. + try { + prepareOperation(getRoot(), monitor); + beginOperation(false); + saveManager.requestSnapshot(); + message = Messages.resources_snapRequest; + return new ResourceStatus(IStatus.OK, message); + } finally { + endOperation(getRoot(), false, null); + } + } + + public void setCrashed(boolean value) { + crashed = value; + if (crashed) { + String msg = "The workspace exited with unsaved changes in the previous session; refreshing workspace to recover changes."; //$NON-NLS-1$ + Policy.log(new ResourceStatus(ICoreConstants.CRASH_DETECTED, msg)); + if (Policy.DEBUG) + Policy.debug(msg); + } + } + + @Override + public void setDescription(IWorkspaceDescription value) { + // if both the old and new description's build orders are null, leave the + // workspace's build order slot because it is caching the computed order. + // Otherwise, set the slot to null to force recomputing or building from the description. + WorkspaceDescription newDescription = (WorkspaceDescription) value; + String[] newOrder = newDescription.getBuildOrder(false); + if (description.getBuildOrder(false) != null || newOrder != null) + buildOrder = null; + description.copyFrom(newDescription); + ResourcesPlugin.getPlugin().savePluginPreferences(); + } + + public void setTreeLocked(boolean locked) { + Assert.isTrue(!locked || treeLocked == null, "The workspace tree is already locked"); //$NON-NLS-1$ + treeLocked = locked ? Thread.currentThread() : null; + } + + /** + * Shuts down the workspace managers. + */ + protected void shutdown(IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + IManager[] managers = {buildManager, propertyManager, pathVariableManager, charsetManager, fileSystemManager, markerManager, _workManager, aliasManager, refreshManager, contentDescriptionManager, natureManager, filterManager}; + monitor.beginTask("", managers.length); //$NON-NLS-1$ + String message = Messages.resources_shutdownProblems; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + // best effort to shutdown every object and free resources + for (int i = 0; i < managers.length; i++) { + IManager manager = managers[i]; + if (manager == null) + monitor.worked(1); + else { + try { + manager.shutdown(Policy.subMonitorFor(monitor, 1)); + } catch (Exception e) { + message = Messages.resources_shutdownProblems; + status.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, e)); + } + } + } + buildManager = null; + notificationManager = null; + propertyManager = null; + pathVariableManager = null; + fileSystemManager = null; + markerManager = null; + synchronizer = null; + saveManager = null; + _workManager = null; + aliasManager = null; + refreshManager = null; + charsetManager = null; + contentDescriptionManager = null; + if (!status.isOK()) + throw new CoreException(status); + } finally { + monitor.done(); + } + } + + @Override + public String[] sortNatureSet(String[] natureIds) { + return natureManager.sortNatureSet(natureIds); + } + + /** + * Starts all the workspace manager classes. + */ + protected void startup(IProgressMonitor monitor) throws CoreException { + // ensure the tree is locked during the startup notification + try { + _workManager = new WorkManager(this); + _workManager.startup(null); + fileSystemManager = new FileSystemResourceManager(this); + fileSystemManager.startup(monitor); + pathVariableManager = new PathVariableManager(); + pathVariableManager.startup(null); + natureManager = new NatureManager(); + natureManager.startup(null); + filterManager = new FilterTypeManager(); + filterManager.startup(null); + buildManager = new BuildManager(this, getWorkManager().getLock()); + buildManager.startup(null); + notificationManager = new NotificationManager(this); + notificationManager.startup(null); + markerManager = new MarkerManager(this); + markerManager.startup(null); + synchronizer = new Synchronizer(this); + saveManager = new SaveManager(this); + saveManager.startup(null); + propertyManager = new PropertyManager2((Workspace) ResourcesPlugin.getWorkspace()); + propertyManager.startup(monitor); + charsetManager = new CharsetManager(this); + charsetManager.startup(null); + contentDescriptionManager = new ContentDescriptionManager(); + contentDescriptionManager.startup(null); + //must start after save manager, because (read) access to tree is needed + //must start after other managers to avoid potential cyclic dependency on uninitialized managers (see bug 316182) + //must start before alias manager (see bug 94829) + refreshManager = new RefreshManager(this); + refreshManager.startup(null); + //must start at the end to avoid potential cyclic dependency on other uninitialized managers (see bug 369177) + aliasManager = new AliasManager(this); + aliasManager.startup(null); + } finally { + //unlock tree even in case of failure, otherwise shutdown will also fail + treeLocked = null; + _workManager.postWorkspaceStartup(); + } + } + + /** + * Returns a string representation of this working state's + * structure suitable for debug purposes. + */ + public String toDebugString() { + final StringBuffer buffer = new StringBuffer("\nDump of " + toString() + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$ + buffer.append(" parent: " + tree.getParent()); //$NON-NLS-1$ + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree aTree, IPathRequestor requestor, Object elementContents) { + buffer.append("\n " + requestor.requestPath() + ": " + elementContents); //$NON-NLS-1$ //$NON-NLS-2$ + return true; + } + }; + new ElementTreeIterator(tree, Path.ROOT).iterate(visitor); + return buffer.toString(); + } + + public void updateModificationStamp(ResourceInfo info) { + info.incrementModificationStamp(); + } + + @Override + public IStatus validateEdit(final IFile[] files, final Object context) { + // if validation is turned off then just return + if (!shouldValidate) { + String message = Messages.resources_readOnly2; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.READ_ONLY_LOCAL, message, null); + for (int i = 0; i < files.length; i++) { + if (files[i].isReadOnly()) { + IPath filePath = files[i].getFullPath(); + message = NLS.bind(Messages.resources_readOnly, filePath); + result.add(new ResourceStatus(IResourceStatus.READ_ONLY_LOCAL, filePath, message)); + } + } + return result.getChildren().length == 0 ? Status.OK_STATUS : (IStatus) result; + } + // first time through the validator hasn't been initialized so try and create it + if (validator == null) + initializeValidator(); + // we were unable to initialize the validator. Validation has been turned off and + // a warning has already been logged so just return. + if (validator == null) + return Status.OK_STATUS; + // otherwise call the API and throw an exception if appropriate + final IStatus[] status = new IStatus[1]; + ISafeRunnable body = new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + status[0] = new ResourceStatus(IStatus.ERROR, null, Messages.resources_errorValidator, exception); + } + + @Override + public void run() throws Exception { + Object c = context; + //must null any reference to FileModificationValidationContext for backwards compatibility + if (!(validator instanceof FileModificationValidator)) + if (c instanceof FileModificationValidationContext) + c = null; + status[0] = validator.validateEdit(files, c); + } + }; + SafeRunner.run(body); + return status[0]; + } + + @Override + public IStatus validateLinkLocation(IResource resource, IPath unresolvedLocation) { + return locationValidator.validateLinkLocation(resource, unresolvedLocation); + } + + @Override + public IStatus validateLinkLocationURI(IResource resource, URI unresolvedLocation) { + return locationValidator.validateLinkLocationURI(resource, unresolvedLocation); + } + + @Override + public IStatus validateName(String segment, int type) { + return locationValidator.validateName(segment, type); + } + + @Override + public IStatus validateNatureSet(String[] natureIds) { + return natureManager.validateNatureSet(natureIds); + } + + @Override + public IStatus validatePath(String path, int type) { + return locationValidator.validatePath(path, type); + } + + @Override + public IStatus validateProjectLocation(IProject context, IPath location) { + return locationValidator.validateProjectLocation(context, location); + } + + @Override + public IStatus validateProjectLocationURI(IProject project, URI location) { + return locationValidator.validateProjectLocationURI(project, location); + } + + /** + * Internal method. To be called only from the following methods: + *
            + *
          • IFile#appendContents
          • + *
          • IFile#setContents(InputStream, boolean, boolean, IProgressMonitor)
          • + *
          • IFile#setContents(IFileState, boolean, boolean, IProgressMonitor)
          • + *
          + * + * @see IFileModificationValidator#validateSave(IFile) + */ + protected void validateSave(final IFile file) throws CoreException { + // if validation is turned off then just return + if (!shouldValidate) + return; + // first time through the validator hasn't been initialized so try and create it + if (validator == null) + initializeValidator(); + // we were unable to initialize the validator. Validation has been turned off and + // a warning has already been logged so just return. + if (validator == null) + return; + // otherwise call the API and throw an exception if appropriate + final IStatus[] status = new IStatus[1]; + ISafeRunnable body = new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + status[0] = new ResourceStatus(IStatus.ERROR, null, Messages.resources_errorValidator, exception); + } + + @Override + public void run() throws Exception { + status[0] = validator.validateSave(file); + } + }; + SafeRunner.run(body); + if (!status[0].isOK()) + throw new ResourceException(status[0]); + } + + @Override + public IStatus validateFiltered(IResource resource) { + try { + if (((Resource) resource).isFilteredWithException(true)) + return new ResourceStatus(IStatus.ERROR, Messages.resources_errorResourceIsFiltered); + } catch (CoreException e) { + // if we can't validate it, we return OK + } + return Status.OK_STATUS; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java new file mode 100644 index 0000000000..78d21a7d57 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.IWorkspaceDescription; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.preferences.DefaultScope; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; + +/** + * @see IWorkspaceDescription + */ +public class WorkspaceDescription extends ModelObject implements IWorkspaceDescription { + protected boolean autoBuilding; + protected String[] buildOrder; + protected long fileStateLongevity; + protected int maxBuildIterations; + protected int maxFileStates; + protected long maxFileStateSize; + protected boolean applyFileStatePolicy; + private long snapshotInterval; + protected int operationsPerSnapshot; + protected long deltaExpiration; + + public WorkspaceDescription(String name) { + super(name); + // initialize based on the values in the default preferences + IEclipsePreferences node = DefaultScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES); + autoBuilding = node.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING, PreferenceInitializer.PREF_AUTO_BUILDING_DEFAULT); + maxBuildIterations = node.getInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS, PreferenceInitializer.PREF_MAX_BUILD_ITERATIONS_DEFAULT); + applyFileStatePolicy = node.getBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY, PreferenceInitializer.PREF_APPLY_FILE_STATE_POLICY_DEFAULT); + fileStateLongevity = node.getLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, PreferenceInitializer.PREF_FILE_STATE_LONGEVITY_DEFAULT); + maxFileStates = node.getInt(ResourcesPlugin.PREF_MAX_FILE_STATES, PreferenceInitializer.PREF_MAX_FILE_STATES_DEFAULT); + maxFileStateSize = node.getLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, PreferenceInitializer.PREF_MAX_FILE_STATE_SIZE_DEFAULT); + snapshotInterval = node.getLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL, PreferenceInitializer.PREF_SNAPSHOT_INTERVAL_DEFAULT); + operationsPerSnapshot = node.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT, PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT_DEFAULT); + deltaExpiration = node.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION, PreferenceInitializer.PREF_DELTA_EXPIRATION_DEFAULT); + } + + /** + * @see IWorkspaceDescription#getBuildOrder() + */ + @Override + public String[] getBuildOrder() { + return getBuildOrder(true); + } + + public String[] getBuildOrder(boolean makeCopy) { + if (buildOrder == null) + return null; + return makeCopy ? (String[]) buildOrder.clone() : buildOrder; + } + + public long getDeltaExpiration() { + return deltaExpiration; + } + + public void setDeltaExpiration(long value) { + deltaExpiration = value; + } + + /** + * @see IWorkspaceDescription#getFileStateLongevity() + */ + @Override + public long getFileStateLongevity() { + return fileStateLongevity; + } + + /** + * @see IWorkspaceDescription#getMaxBuildIterations() + */ + @Override + public int getMaxBuildIterations() { + return maxBuildIterations; + } + + /** + * @see IWorkspaceDescription#getMaxFileStates() + */ + @Override + public int getMaxFileStates() { + return maxFileStates; + } + + /** + * @see IWorkspaceDescription#getMaxFileStateSize() + */ + @Override + public long getMaxFileStateSize() { + return maxFileStateSize; + } + + /** + * @see IWorkspaceDescription#isApplyFileStatePolicy() + */ + @Override + public boolean isApplyFileStatePolicy() { + return applyFileStatePolicy; + } + + public int getOperationsPerSnapshot() { + return operationsPerSnapshot; + } + + /** + * @see IWorkspaceDescription#getSnapshotInterval() + */ + @Override + public long getSnapshotInterval() { + return snapshotInterval; + } + + public void internalSetBuildOrder(String[] value) { + buildOrder = value; + } + + /** + * @see IWorkspaceDescription#isAutoBuilding() + */ + @Override + public boolean isAutoBuilding() { + return autoBuilding; + } + + public void setOperationsPerSnapshot(int value) { + operationsPerSnapshot = value; + } + + /** + * @see IWorkspaceDescription#setAutoBuilding(boolean) + */ + @Override + public void setAutoBuilding(boolean value) { + autoBuilding = value; + } + + /** + * @see IWorkspaceDescription#setBuildOrder(String[]) + */ + @Override + public void setBuildOrder(String[] value) { + buildOrder = (value == null) ? null : (String[]) value.clone(); + } + + /** + * @see IWorkspaceDescription#setFileStateLongevity(long) + */ + @Override + public void setFileStateLongevity(long time) { + fileStateLongevity = time; + } + + /** + * @see IWorkspaceDescription#setMaxBuildIterations(int) + */ + @Override + public void setMaxBuildIterations(int number) { + maxBuildIterations = number; + } + + /** + * @see IWorkspaceDescription#setMaxFileStates(int) + */ + @Override + public void setMaxFileStates(int number) { + maxFileStates = number; + } + + /** + * @see IWorkspaceDescription#setMaxFileStateSize(long) + */ + @Override + public void setMaxFileStateSize(long size) { + maxFileStateSize = size; + } + + /** + * @see IWorkspaceDescription#setApplyFileStatePolicy(boolean) + */ + @Override + public void setApplyFileStatePolicy(boolean apply) { + applyFileStatePolicy = apply; + } + + /** + * @see IWorkspaceDescription#setSnapshotInterval(long) + */ + @Override + public void setSnapshotInterval(long snapshotInterval) { + this.snapshotInterval = snapshotInterval; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java new file mode 100644 index 0000000000..dfea8b6c06 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import javax.xml.parsers.*; +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.resources.IResourceStatus; +import org.eclipse.core.runtime.IPath; +import org.eclipse.osgi.util.NLS; +import org.w3c.dom.*; +import org.xml.sax.SAXException; + +/** + * This class contains legacy code only. It is being used to read workspace + * descriptions which are obsolete. + */ +public class WorkspaceDescriptionReader implements IModelObjectConstants { + /** constants */ + protected static final String[] EMPTY_STRING_ARRAY = new String[0]; + + public WorkspaceDescriptionReader() { + super(); + } + + protected String getString(Node target, String tagName) { + Node node = searchNode(target, tagName); + return node != null ? (node.getFirstChild() == null ? null : node.getFirstChild().getNodeValue()) : null; + } + + protected String[] getStrings(Node target) { + if (target == null) + return null; + NodeList list = target.getChildNodes(); + if (list.getLength() == 0) + return EMPTY_STRING_ARRAY; + List result = new ArrayList<>(list.getLength()); + for (int i = 0; i < list.getLength(); i++) { + Node node = list.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) + result.add(read(node.getChildNodes().item(0))); + } + return result.toArray(new String[result.size()]); + } + + /** + * A value was discovered in the workspace description file that was not a number. + * Log the exception. + */ + private void logNumberFormatException(String value, NumberFormatException e) { + String msg = NLS.bind(Messages.resources_readWorkspaceMetaValue, value); + Policy.log(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, null, msg, e)); + } + + public Object read(InputStream input) { + try { + DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = parser.parse(input); + return read(document.getFirstChild()); + } catch (IOException e) { + // ignore + } catch (SAXException e) { + // ignore + } catch (ParserConfigurationException e) { + // ignore + } + return null; + } + + public Object read(IPath location, IPath tempLocation) throws IOException { + SafeFileInputStream file = new SafeFileInputStream(location.toOSString(), tempLocation.toOSString()); + try { + return read(file); + } finally { + file.close(); + } + } + + protected Object read(Node node) { + if (node == null) + return null; + switch (node.getNodeType()) { + case Node.ELEMENT_NODE : + if (node.getNodeName().equals(WORKSPACE_DESCRIPTION)) + return readWorkspaceDescription(node); + case Node.TEXT_NODE : + String value = node.getNodeValue(); + return value == null ? null : value.trim(); + default : + return node.toString(); + } + } + + /** + * read (String, String) hashtables + */ + protected WorkspaceDescription readWorkspaceDescription(Node node) { + // get values + String name = getString(node, NAME); + String autobuild = getString(node, AUTOBUILD); + String snapshotInterval = getString(node, SNAPSHOT_INTERVAL); + String applyFileStatePolicy = getString(node, APPLY_FILE_STATE_POLICY); + String fileStateLongevity = getString(node, FILE_STATE_LONGEVITY); + String maxFileStateSize = getString(node, MAX_FILE_STATE_SIZE); + String maxFileStates = getString(node, MAX_FILE_STATES); + String[] buildOrder = getStrings(searchNode(node, BUILD_ORDER)); + + // build instance + //invalid values are skipped and defaults are used instead + WorkspaceDescription description = new WorkspaceDescription(name); + if (autobuild != null) + //if in doubt (value is corrupt) we want autobuild on + description.setAutoBuilding(!autobuild.equals(Integer.toString(0))); + if (applyFileStatePolicy != null) + //if in doubt (value is corrupt) we want applyFileLimits on + description.setApplyFileStatePolicy(!applyFileStatePolicy.equals(Integer.toString(0))); + try { + if (fileStateLongevity != null) + description.setFileStateLongevity(Long.parseLong(fileStateLongevity)); + } catch (NumberFormatException e) { + logNumberFormatException(fileStateLongevity, e); + } + try { + if (maxFileStateSize != null) + description.setMaxFileStateSize(Long.parseLong(maxFileStateSize)); + } catch (NumberFormatException e) { + logNumberFormatException(maxFileStateSize, e); + } + try { + if (maxFileStates != null) + description.setMaxFileStates(Integer.parseInt(maxFileStates)); + } catch (NumberFormatException e) { + logNumberFormatException(maxFileStates, e); + } + if (buildOrder != null) + description.internalSetBuildOrder(buildOrder); + try { + if (snapshotInterval != null) + description.setSnapshotInterval(Long.parseLong(snapshotInterval)); + } catch (NumberFormatException e) { + logNumberFormatException(snapshotInterval, e); + } + return description; + } + + protected Node searchNode(Node target, String tagName) { + NodeList list = target.getChildNodes(); + for (int i = 0; i < list.getLength(); i++) { + if (list.item(i).getNodeName().equals(tagName)) + return list.item(i); + } + return null; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java new file mode 100644 index 0000000000..8e0659ef13 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java @@ -0,0 +1,257 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Preferences; +import org.eclipse.core.runtime.Preferences.PropertyChangeEvent; + +/** + * This class provides the same interface as WorkspaceDescription + * but instead of changing/obtaining values from its internal state, it + * changes/obtains properties in/from the workspace plug-in's preferences. + * + * Note: for performance reasons, some frequently called accessor methods are + * reading a cached value from the super class instead of reading the + * corresponding property preference store. To keep the cache synchronized with + * the preference store, a property change listener is used. + */ +public class WorkspacePreferences extends WorkspaceDescription { + + public final static String PROJECT_SEPARATOR = "/"; //$NON-NLS-1$ + + private Preferences preferences; + + /** + * Helper method that converts a string string array {"string1"," + * string2",..."stringN"} to a string in the form "string1,string2,... + * stringN". + */ + public static String convertStringArraytoString(String[] array) { + if (array == null || array.length == 0) + return ""; //$NON-NLS-1$ + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < array.length; i++) { + sb.append(array[i]); + sb.append(PROJECT_SEPARATOR); + } + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); + } + + /** + * Helper method that converts a string in the form "string1,string2,... + * stringN" to a string array {"string1","string2",..."stringN"}. + */ + public static String[] convertStringToStringArray(String string, String separator) { + List list = new ArrayList<>(); + for (StringTokenizer tokenizer = new StringTokenizer(string, separator); tokenizer.hasMoreTokens();) + list.add(tokenizer.nextToken()); + return list.toArray(new String[list.size()]); + } + + /** + * Helper method that copies all attributes from a workspace description + * object to another. + */ + private static void copyFromTo(WorkspaceDescription source, WorkspaceDescription target) { + target.setAutoBuilding(source.isAutoBuilding()); + target.setBuildOrder(source.getBuildOrder()); + target.setMaxBuildIterations(source.getMaxBuildIterations()); + target.setApplyFileStatePolicy(source.isApplyFileStatePolicy()); + target.setFileStateLongevity(source.getFileStateLongevity()); + target.setMaxFileStates(source.getMaxFileStates()); + target.setMaxFileStateSize(source.getMaxFileStateSize()); + target.setSnapshotInterval(source.getSnapshotInterval()); + target.setOperationsPerSnapshot(source.getOperationsPerSnapshot()); + target.setDeltaExpiration(source.getDeltaExpiration()); + } + + public WorkspacePreferences() { + super("Workspace"); //$NON-NLS-1$ + this.preferences = ResourcesPlugin.getPlugin().getPluginPreferences(); + + final String version = preferences.getString(ICoreConstants.PREF_VERSION_KEY); + if (!ICoreConstants.PREF_VERSION.equals(version)) + upgradeVersion(version); + + // initialize cached preferences (for better performance) + super.setAutoBuilding(preferences.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING)); + super.setSnapshotInterval(preferences.getInt(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL)); + super.setMaxBuildIterations(preferences.getInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS)); + super.setApplyFileStatePolicy(preferences.getBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY)); + super.setMaxFileStates(preferences.getInt(ResourcesPlugin.PREF_MAX_FILE_STATES)); + super.setMaxFileStateSize(preferences.getLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE)); + super.setFileStateLongevity(preferences.getLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY)); + super.setOperationsPerSnapshot(preferences.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT)); + super.setDeltaExpiration(preferences.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION)); + + // This property listener ensures we are being updated properly when changes + // are done directly to the preference store. + preferences.addPropertyChangeListener(new Preferences.IPropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + synchronizeWithPreferences(event.getProperty()); + } + }); + } + + @Override + public Object clone() { + // should never be called - throws an exception to avoid using a + // WorkspacePreferences when using WorkspaceDescription was the real + // intention (this class offers a different protocol for copying state). + throw new UnsupportedOperationException("clone() is not supported in " + getClass().getName()); //$NON-NLS-1$ + } + + public void copyFrom(WorkspaceDescription source) { + copyFromTo(source, this); + } + + public void copyTo(WorkspaceDescription target) { + copyFromTo(this, target); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#getBuildOrder() + */ + @Override + public String[] getBuildOrder() { + boolean defaultBuildOrder = preferences.getBoolean(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER); + if (defaultBuildOrder) + return null; + return convertStringToStringArray(preferences.getString(ResourcesPlugin.PREF_BUILD_ORDER), PROJECT_SEPARATOR); + } + + /** + * @see org.eclipse.core.internal.resources. + * WorkspaceDescription#getBuildOrder(boolean) + */ + @Override + public String[] getBuildOrder(boolean makeCopy) { + //note that since this is stored in the preference store, we are creating + //a new copy of the string array on every access anyway + return getBuildOrder(); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setAutoBuilding(boolean) + */ + @Override + public void setAutoBuilding(boolean value) { + preferences.setValue(ResourcesPlugin.PREF_AUTO_BUILDING, value); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setBuildOrder(String[]) + */ + @Override + public void setBuildOrder(String[] value) { + preferences.setValue(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER, value == null); + preferences.setValue(ResourcesPlugin.PREF_BUILD_ORDER, convertStringArraytoString(value)); + } + + @Override + public void setDeltaExpiration(long value) { + preferences.setValue(PreferenceInitializer.PREF_DELTA_EXPIRATION, value); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setApplyFileStatePolicy(boolean) + */ + @Override + public void setApplyFileStatePolicy(boolean apply) { + preferences.setValue(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY, apply); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setFileStateLongevity(long) + */ + @Override + public void setFileStateLongevity(long time) { + preferences.setValue(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, time); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxBuildIterations(int) + */ + @Override + public void setMaxBuildIterations(int number) { + preferences.setValue(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS, number); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxFileStates(int) + */ + @Override + public void setMaxFileStates(int number) { + preferences.setValue(ResourcesPlugin.PREF_MAX_FILE_STATES, number); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxFileStateSize(long) + */ + @Override + public void setMaxFileStateSize(long size) { + preferences.setValue(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, size); + } + + @Override + public void setOperationsPerSnapshot(int value) { + preferences.setValue(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT, value); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setSnapshotInterval(long) + */ + @Override + public void setSnapshotInterval(long delay) { + preferences.setValue(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL, delay); + } + + protected void synchronizeWithPreferences(String property) { + // do not use the value in the event - may be a string instead + // of the expected type. Retrieve it from the preferences store + // using the type-specific method + if (property.equals(ResourcesPlugin.PREF_AUTO_BUILDING)) + super.setAutoBuilding(preferences.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING)); + else if (property.equals(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL)) + super.setSnapshotInterval(preferences.getLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL)); + else if (property.equals(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS)) + super.setMaxBuildIterations(preferences.getInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS)); + else if (property.equals(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY)) + super.setApplyFileStatePolicy(preferences.getBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY)); + else if (property.equals(ResourcesPlugin.PREF_MAX_FILE_STATES)) + super.setMaxFileStates(preferences.getInt(ResourcesPlugin.PREF_MAX_FILE_STATES)); + else if (property.equals(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE)) + super.setMaxFileStateSize(preferences.getLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE)); + else if (property.equals(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY)) + super.setFileStateLongevity(preferences.getLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY)); + else if (property.equals(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT)) + super.setOperationsPerSnapshot(preferences.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT)); + else if (property.equals(PreferenceInitializer.PREF_DELTA_EXPIRATION)) + super.setDeltaExpiration(preferences.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION)); + } + + private void upgradeVersion(String oldVersion) { + if (oldVersion.length() == 0) { + //only need to convert the build order if we are not using the default order + if (!preferences.getBoolean(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER)) { + String oldOrder = preferences.getString(ResourcesPlugin.PREF_BUILD_ORDER); + setBuildOrder(convertStringToStringArray(oldOrder, ":")); //$NON-NLS-1$ + } + } + preferences.setValue(ICoreConstants.PREF_VERSION_KEY, ICoreConstants.PREF_VERSION); + } +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java new file mode 100644 index 0000000000..7007b33f7c --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java @@ -0,0 +1,295 @@ +/******************************************************************************* + * 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.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.utils.FileUtil; +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 WorkspaceRoot extends Container implements IWorkspaceRoot { + /** + * As an optimization, we store a table of project handles + * that have been requested from this root. This maps project + * name strings to project handles. + */ + private final Map projectTable = Collections.synchronizedMap(new HashMap(16)); + + /** + * Cache of the canonicalized platform location. + */ + private final IPath workspaceLocation; + + protected WorkspaceRoot(IPath path, Workspace container) { + super(path, container); + Assert.isTrue(path.equals(Path.ROOT)); + workspaceLocation = FileUtil.canonicalPath(Platform.getLocation()); + Assert.isNotNull(workspaceLocation); + } + + @Override + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT; + delete(updateFlags, monitor); + } + + @Override + public void delete(boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + delete(updateFlags, monitor); + } + + @Override + public boolean exists(int flags, boolean checkType) { + return true; + } + + @Deprecated + @Override + public IContainer[] findContainersForLocation(IPath location) { + return findContainersForLocationURI(URIUtil.toURI(location.makeAbsolute())); + } + + @Override + public IContainer[] findContainersForLocationURI(URI location) { + return findContainersForLocationURI(location, NONE); + } + + @Override + public IContainer[] findContainersForLocationURI(URI location, int memberFlags) { + if (!location.isAbsolute()) + throw new IllegalArgumentException(); + return (IContainer[]) getLocalManager().allResourcesFor(location, false, memberFlags); + } + + @Deprecated + @Override + public IFile[] findFilesForLocation(IPath location) { + return findFilesForLocationURI(URIUtil.toURI(location.makeAbsolute())); + } + + @Override + public IFile[] findFilesForLocationURI(URI location) { + return findFilesForLocationURI(location, NONE); + } + + @Override + public IFile[] findFilesForLocationURI(URI location, int memberFlags) { + if (!location.isAbsolute()) + throw new IllegalArgumentException(); + return (IFile[]) getLocalManager().allResourcesFor(location, true, memberFlags); + } + + @Override + public IContainer getContainerForLocation(IPath location) { + return getLocalManager().containerForLocation(location); + } + + @Override + public String getDefaultCharset(boolean checkImplicit) { + if (checkImplicit) + return ResourcesPlugin.getEncoding(); + String enc = ResourcesPlugin.getPlugin().getPluginPreferences().getString(ResourcesPlugin.PREF_ENCODING); + return enc == null || enc.length() == 0 ? null : enc; + } + + @Override + public IFile getFileForLocation(IPath location) { + return getLocalManager().fileForLocation(location); + } + + @Override + public long getLocalTimeStamp() { + return IResource.NULL_STAMP; + } + + @Override + public IPath getLocation() { + return workspaceLocation; + } + + @Override + public String getName() { + return ""; //$NON-NLS-1$ + } + + @Override + public IContainer getParent() { + return null; + } + + @Override + public IProject getProject() { + return null; + } + + @Override + public IProject getProject(String name) { + //first check our project cache + Project result = projectTable.get(name); + if (result == null) { + IPath projectPath = new Path(null, name).makeAbsolute(); + String message = "Path for project must have only one segment."; //$NON-NLS-1$ + Assert.isLegal(projectPath.segmentCount() == ICoreConstants.PROJECT_SEGMENT_LENGTH, message); + //try to get the project using a canonical name + String canonicalName = projectPath.lastSegment(); + result = projectTable.get(canonicalName); + if (result != null) + return result; + result = new Project(projectPath, workspace); + projectTable.put(canonicalName, result); + } + return result; + } + + @Override + public IPath getProjectRelativePath() { + return Path.EMPTY; + } + + @Override + public IProject[] getProjects() { + return getProjects(IResource.NONE); + } + + @Override + public IProject[] getProjects(int memberFlags) { + IResource[] roots = getChildren(memberFlags); + IProject[] result = new IProject[roots.length]; + try { + System.arraycopy(roots, 0, result, 0, roots.length); + } catch (ArrayStoreException ex) { + // Shouldn't happen since only projects should be children of the workspace root + for (int i = 0; i < roots.length; i++) { + if (roots[i].getType() != IResource.PROJECT) + Policy.log(IStatus.ERROR, NLS.bind("{0} is an invalid child of the workspace root.", //$NON-NLS-1$ + roots[i]), null); + + } + throw ex; + } + return result; + } + + @Override + public int getType() { + return IResource.ROOT; + } + + @Override + public void internalSetLocal(boolean flag, int depth) throws CoreException { + // do nothing for the root, but call for its children + if (depth == IResource.DEPTH_ZERO) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.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++) + ((Resource) children[i]).internalSetLocal(flag, depth); + } + + @Override + public boolean isDerived(int options) { + return false;//the root is never derived + } + + @Override + public boolean isHidden() { + return false;//the root is never hidden + } + + @Override + public boolean isHidden(int options) { + return false;//the root is never hidden + } + + @Override + public boolean isTeamPrivateMember(int options) { + return false;//the root is never a team private member + } + + @Override + public boolean isLinked(int options) { + return false;//the root is never linked + } + + @Deprecated + @Override + public boolean isLocal(int depth) { + // the flags parameter is ignored for the workspace root so pass anything + return isLocal(-1, depth); + } + + @Deprecated + @Override + public boolean isLocal(int flags, int depth) { + // don't check the flags....workspace root is always local + 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 boolean isPhantom() { + return false; + } + + @Deprecated + @Override + public void setDefaultCharset(String charset) { + // directly change the Resource plugin's preference for encoding + Preferences resourcesPreferences = ResourcesPlugin.getPlugin().getPluginPreferences(); + if (charset != null) + resourcesPreferences.setValue(ResourcesPlugin.PREF_ENCODING, charset); + else + resourcesPreferences.setToDefault(ResourcesPlugin.PREF_ENCODING); + } + + @Override + public void setHidden(boolean isHidden) { + //workspace root cannot be set hidden + } + + @Override + public long setLocalTimeStamp(long value) { + if (value < 0) + throw new IllegalArgumentException("Illegal time stamp: " + value); //$NON-NLS-1$ + //can't set local time for root + return value; + } + + @Deprecated + @Override + public void setReadOnly(boolean readonly) { + //can't set the root read only + } + + @Override + public void touch(IProgressMonitor monitor) { + // do nothing for the workspace root + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java new file mode 100644 index 0000000000..db7f637c58 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 - Initial API and implementation + * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.watson.ElementTree; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.osgi.util.NLS; + +/** + * Default tree reader that does not read anything. This is used in cases + * where the tree format is unknown (for example when opening a workspace + * from a future version). + */ +public abstract class WorkspaceTreeReader { + + /** + * Configuration setting to have an existing workspace + * project name take precedence over data being read, + * when set to true. + */ + protected boolean renameProjectNode; + + /** + * Returns the tree reader associated with the given tree version number. + * @param renameProjectNode if true, set up the reader to have + * the existing root node in the workspace (that is, the project being + * read into) take precedence over the root node being read from the file. + * Otherwise, the tree file is read unmodified. + */ + public static WorkspaceTreeReader getReader(Workspace workspace, int version, boolean renameProjectNode) throws CoreException { + WorkspaceTreeReader w = null; + switch (version) { + case ICoreConstants.WORKSPACE_TREE_VERSION_1 : + w = new WorkspaceTreeReader_1(workspace); + w.renameProjectNode = renameProjectNode; + return w; + case ICoreConstants.WORKSPACE_TREE_VERSION_2 : + w = new WorkspaceTreeReader_2(workspace); + w.renameProjectNode = renameProjectNode; + return w; + default : + // Unknown tree version - fail to read the tree + String msg = NLS.bind(Messages.resources_format, version); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, msg, null); + } + } + + /** + * Returns the tree reader associated with the given tree version number. + */ + public static WorkspaceTreeReader getReader(Workspace workspace, int version) throws CoreException { + return getReader(workspace, version, false); + } + + /** + * Returns a snapshot from the stream. This default implementation does nothing. + */ + public abstract ElementTree readSnapshotTree(DataInputStream input, ElementTree complete, IProgressMonitor monitor) throws CoreException; + + /** + * Reads all workspace trees from the stream. This default implementation does nothing. + */ + public abstract void readTree(DataInputStream input, IProgressMonitor monitor) throws CoreException; + + /** + * Reads a project's trees from the stream. This default implementation does nothing. + */ + public abstract void readTree(IProject project, DataInputStream input, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java new file mode 100644 index 0000000000..d8d2362e01 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java @@ -0,0 +1,271 @@ +/******************************************************************************* + * 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 + * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.*; +import org.eclipse.core.internal.events.BuilderPersistentInfo; +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.internal.watson.ElementTreeReader; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; + +/** + * Reads version 1 of the workspace tree file format. + */ +public class WorkspaceTreeReader_1 extends WorkspaceTreeReader { + protected Workspace workspace; + + public WorkspaceTreeReader_1(Workspace workspace) { + this.workspace = workspace; + } + + protected int getVersion() { + return ICoreConstants.WORKSPACE_TREE_VERSION_1; + } + + protected void linkBuildersToTrees(List buildersToBeLinked, ElementTree[] trees, int index, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + ArrayList infos = null; + String projectName = null; + for (int i = 0; i < buildersToBeLinked.size(); i++) { + BuilderPersistentInfo info = buildersToBeLinked.get(i); + if (!info.getProjectName().equals(projectName)) { + if (infos != null) { // if it is not the first iteration + IProject project = workspace.getRoot().getProject(projectName); + workspace.getBuildManager().setBuildersPersistentInfo(project, infos); + } + projectName = info.getProjectName(); + infos = new ArrayList<>(5); + } + info.setLastBuildTree(trees[index++]); + infos.add(info); + } + if (infos != null) { + IProject project = workspace.getRoot().getProject(projectName); + workspace.getBuildManager().setBuildersPersistentInfo(project, infos); + } + } finally { + monitor.done(); + } + } + + protected void linkPluginsSavedStateToTrees(List states, ElementTree[] trees, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + for (int i = 0; i < states.size(); i++) { + SavedState state = states.get(i); + // If the tree is too old (depends on the policy), the plug-in should not + // get it back as a delta. It is expensive to maintain this information too long. + final SaveManager saveManager = workspace.getSaveManager(); + if (!saveManager.isOldPluginTree(state.pluginId)) { + state.oldTree = trees[i]; + } else { + //clear information for this plugin from master table + saveManager.clearDeltaExpiration(state.pluginId); + } + } + } finally { + monitor.done(); + } + } + + protected BuilderPersistentInfo readBuilderInfo(IProject project, DataInputStream input, int index) throws IOException { + //read the project name + String projectName = input.readUTF(); + //use the name of the project handle if available + if (project != null) + projectName = project.getName(); + String builderName = input.readUTF(); + return new BuilderPersistentInfo(projectName, builderName, index); + } + + protected void readBuildersPersistentInfo(IProject project, DataInputStream input, List builders, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + int builderCount = input.readInt(); + for (int i = 0; i < builderCount; i++) + builders.add(readBuilderInfo(project, input, i)); + } finally { + monitor.done(); + } + } + + protected void readPluginsSavedStates(DataInputStream input, HashMap savedStates, List plugins, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + int stateCount = input.readInt(); + for (int i = 0; i < stateCount; i++) { + String pluginId = input.readUTF(); + SavedState state = new SavedState(workspace, pluginId, null, null); + savedStates.put(pluginId, state); + plugins.add(state); + } + } finally { + monitor.done(); + } + } + + @Override + public ElementTree readSnapshotTree(DataInputStream input, ElementTree complete, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_readingSnap; + monitor.beginTask(message, Policy.totalWork); + ElementTreeReader reader = new ElementTreeReader(workspace.getSaveManager()); + while (input.available() > 0) { + readWorkspaceFields(input, Policy.subMonitorFor(monitor, Policy.totalWork / 2)); + complete = reader.readDelta(complete, input); + try { + // make sure each snapshot is read by the correct reader + int version = input.readInt(); + if (version != getVersion()) + return WorkspaceTreeReader.getReader(workspace, version).readSnapshotTree(input, complete, monitor); + } catch (EOFException e) { + break; + } + } + return complete; + } catch (IOException e) { + message = Messages.resources_readWorkspaceSnap; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + @Override + public void readTree(DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, Policy.totalWork); + readWorkspaceFields(input, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + HashMap savedStates = new HashMap<>(20); + List pluginsToBeLinked = new ArrayList<>(20); + readPluginsSavedStates(input, savedStates, pluginsToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + workspace.getSaveManager().setPluginsSavedState(savedStates); + + List buildersToBeLinked = new ArrayList<>(20); + readBuildersPersistentInfo(null, input, buildersToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + ElementTree[] trees = readTrees(Path.ROOT, input, Policy.subMonitorFor(monitor, Policy.opWork * 40 / 100)); + linkPluginsSavedStateToTrees(pluginsToBeLinked, trees, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + linkBuildersToTrees(buildersToBeLinked, trees, pluginsToBeLinked.size(), Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + } catch (IOException e) { + message = Messages.resources_readWorkspaceTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + @Override + public void readTree(IProject project, DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, 10); + /* read the number of builders */ + int numBuilders = input.readInt(); + + /* read in the list of builder names */ + String[] builderNames = new String[numBuilders]; + for (int i = 0; i < numBuilders; i++) { + String builderName = input.readUTF(); + builderNames[i] = builderName; + } + monitor.worked(1); + + /* read and link the trees */ + ElementTree[] trees = readTrees(project.getFullPath(), input, Policy.subMonitorFor(monitor, 8)); + + /* map builder names to trees */ + if (numBuilders > 0) { + ArrayList infos = new ArrayList<>(trees.length * 2 + 1); + for (int i = 0; i < numBuilders; i++) { + BuilderPersistentInfo info = new BuilderPersistentInfo(project.getName(), builderNames[i], -1); + info.setLastBuildTree(trees[i]); + infos.add(info); + } + workspace.getBuildManager().setBuildersPersistentInfo(project, infos); + } + monitor.worked(1); + + } catch (IOException e) { + message = Messages.resources_readProjectTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + /** + * Read trees from disk and link them to the workspace tree. + */ + protected ElementTree[] readTrees(IPath root, DataInputStream input, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + String message = Messages.resources_reading; + monitor.beginTask(message, 4); + ElementTreeReader treeReader = new ElementTreeReader(workspace.getSaveManager()); + String newProjectName = ""; //$NON-NLS-1$ + if (renameProjectNode) { + //have the existing project name (path to import into) take precedence over what we read + newProjectName = root.segment(0); + } + ElementTree[] trees = treeReader.readDeltaChain(input, newProjectName); + monitor.worked(3); + if (root.isRoot()) { + //Don't need to link because we're reading the whole workspace. + //The last tree in the chain is the complete tree. + ElementTree newTree = trees[trees.length - 1]; + newTree.setTreeData(workspace.tree.getTreeData()); + workspace.tree = newTree; + } else { + //splice the restored tree into the current set of trees + workspace.linkTrees(root, trees); + } + monitor.worked(1); + return trees; + } finally { + monitor.done(); + } + } + + protected void readWorkspaceFields(DataInputStream input, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + // read the node id + workspace.nextNodeId = input.readLong(); + // read the modification stamp (no longer used) + input.readLong(); + // read the next marker id + workspace.nextMarkerId = input.readLong(); + // read the synchronizer's registered sync partners + ((Synchronizer) workspace.getSynchronizer()).readPartners(input); + } finally { + monitor.done(); + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java new file mode 100644 index 0000000000..b0e0236029 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java @@ -0,0 +1,219 @@ +/******************************************************************************* + * 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 + * Baltasar Belyavsky (Texas Instruments) - [361675] Order mismatch when saving/restoring workspace trees + * Broadcom Corporation - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.*; +import org.eclipse.core.internal.events.BuilderPersistentInfo; +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.IProject; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; + +/** + * Reads version 2 of the workspace tree file format. + * + * This version differs from version 1 in the amount of information that is persisted + * for each builder. Version 1 only stored builder names and trees. Version + * 2 stores builder names, project names, trees, and interesting projects for + * each builder. + *

          + * Since 3.7 support has been added for persisting multiple delta trees for + * multi-configuration builders. + *

          + *

          + * To achieve backwards compatibility, the new additional information is + * appended to the existing workspace tree file. This allows the workspace + * to be opened, and function, with older eclipse products. + *

          + */ +public class WorkspaceTreeReader_2 extends WorkspaceTreeReader_1 { + + private List builderInfos; + + public WorkspaceTreeReader_2(Workspace workspace) { + super(workspace); + } + + @Override + protected int getVersion() { + return ICoreConstants.WORKSPACE_TREE_VERSION_2; + } + + /* + * overwritten from WorkspaceTreeReader_1 + */ + @Override + protected void readBuildersPersistentInfo(IProject project, DataInputStream input, List builders, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + int builderCount = input.readInt(); + for (int i = 0; i < builderCount; i++) { + BuilderPersistentInfo info = readBuilderInfo(project, input, i); + // read interesting projects + int n = input.readInt(); + IProject[] projects = new IProject[n]; + for (int j = 0; j < n; j++) + projects[j] = workspace.getRoot().getProject(input.readUTF()); + info.setInterestingProjects(projects); + builders.add(info); + } + } finally { + monitor.done(); + } + } + + /** + * Read a workspace tree storing information about multiple projects. + * Overrides {@link WorkspaceTreeReader_1#readTree(DataInputStream, IProgressMonitor)} + */ + @Override + public void readTree(DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, Policy.totalWork); + + builderInfos = new ArrayList<>(20); + + // Read the version 2 part of the file, but don't set the builder info in + // the projects. Store it in builderInfos instead. + readWorkspaceFields(input, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + HashMap savedStates = new HashMap<>(20); + List pluginsToBeLinked = new ArrayList<>(20); + readPluginsSavedStates(input, savedStates, pluginsToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + workspace.getSaveManager().setPluginsSavedState(savedStates); + + int treeIndex = pluginsToBeLinked.size(); + + List buildersToBeLinked = new ArrayList<>(20); + readBuildersPersistentInfo(null, input, buildersToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + final ElementTree[] trees = readTrees(Path.ROOT, input, Policy.subMonitorFor(monitor, Policy.opWork * 40 / 100)); + linkPluginsSavedStateToTrees(pluginsToBeLinked, trees, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + linkBuildersToTrees(buildersToBeLinked, trees, treeIndex, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + // Since 3.7: Read the per-configuration trees if available + if (input.available() > 0) { + treeIndex += buildersToBeLinked.size(); + + buildersToBeLinked.clear(); + readBuildersPersistentInfo(null, input, buildersToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + linkBuildersToTrees(buildersToBeLinked, trees, treeIndex, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + for (Iterator it = builderInfos.iterator(); it.hasNext();) + it.next().setConfigName(input.readUTF()); + } + + // Set the builder infos on the projects + setBuilderInfos(builderInfos); + + } catch (IOException e) { + message = Messages.resources_readProjectTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + /** + * Read a workspace tree storing information about a single project. + * Overrides {@link WorkspaceTreeReader_2#readTree(IProject, DataInputStream, IProgressMonitor)} + */ + @Override + public void readTree(IProject project, DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, 10); + + builderInfos = new ArrayList<>(20); + + // Read the version 2 part of the file, but don't set the builder info in + // the projects. It is stored in builderInfos instead. + + int treeIndex = 0; + + List buildersToBeLinked = new ArrayList<>(20); + readBuildersPersistentInfo(project, input, buildersToBeLinked, Policy.subMonitorFor(monitor, 1)); + + ElementTree[] trees = readTrees(project.getFullPath(), input, Policy.subMonitorFor(monitor, 8)); + linkBuildersToTrees(buildersToBeLinked, trees, treeIndex, Policy.subMonitorFor(monitor, 1)); + + // Since 3.7: Read the additional builder information + if (input.available() > 0) { + treeIndex += buildersToBeLinked.size(); + + List infos = new ArrayList<>(5); + readBuildersPersistentInfo(project, input, infos, Policy.subMonitorFor(monitor, 1)); + linkBuildersToTrees(infos, trees, treeIndex, Policy.subMonitorFor(monitor, 1)); + + for (Iterator it = builderInfos.iterator(); it.hasNext();) + it.next().setConfigName(input.readUTF()); + } + + // Set the builder info on the projects + setBuilderInfos(builderInfos); + + } catch (IOException e) { + message = Messages.resources_readProjectTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + /** + * This implementation allows pre-3.7 version 2 and post-3.7 version 2 information to be loaded in separate passes. + * Links trees with the given builders, but does not add them to the projects. + * Overrides {@link WorkspaceTreeReader_1#linkBuildersToTrees(List, ElementTree[], int, IProgressMonitor)} + */ + @Override + protected void linkBuildersToTrees(List buildersToBeLinked, ElementTree[] trees, int index, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + for (int i = 0; i < buildersToBeLinked.size(); i++) { + BuilderPersistentInfo info = buildersToBeLinked.get(i); + info.setLastBuildTree(trees[index++]); + builderInfos.add(info); + } + } finally { + monitor.done(); + } + } + + /** + * Given a list of builder infos, group them by project and set them on the project. + */ + private void setBuilderInfos(List infos) { + Map> groupedInfos = new HashMap<>(); + for (Iterator it = infos.iterator(); it.hasNext();) { + BuilderPersistentInfo info = it.next(); + if (!groupedInfos.containsKey(info.getProjectName())) + groupedInfos.put(info.getProjectName(), new ArrayList()); + groupedInfos.get(info.getProjectName()).add(info); + } + for (Map.Entry> entry : groupedInfos.entrySet()) { + IProject proj = workspace.getRoot().getProject(entry.getKey()); + workspace.getBuildManager().setBuildersPersistentInfo(proj, entry.getValue()); + } + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java new file mode 100644 index 0000000000..383f3053a3 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * 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.HashMap; +import java.util.Map; + +/** + * A simple XML writer. + */ +public class XMLWriter extends PrintWriter { + protected int tab; + protected String lineSeparator; + + /* constants */ + protected static final String XML_VERSION = ""; //$NON-NLS-1$ + + public XMLWriter(OutputStream output, String separator) throws UnsupportedEncodingException { + super(new OutputStreamWriter(output, "UTF8")); //$NON-NLS-1$ + tab = 0; + lineSeparator = separator; + println(XML_VERSION); + } + + public void endTag(String name) { + tab--; + printTag('/' + name, null); + } + + @Override + public void println(String x) { + super.print(x); + super.print(lineSeparator); + } + + public void printSimpleTag(String name, Object value) { + if (value != null) { + printTag(name, null, true, false); + print(getEscaped(String.valueOf(value))); + printTag('/' + name, null, false, true); + } + } + + public void printTabulation() { + for (int i = 0; i < tab; i++) + super.print('\t'); + } + + public void printTag(String name, HashMap parameters) { + printTag(name, parameters, true, true); + } + + public void printTag(String name, HashMap parameters, boolean shouldTab, boolean newLine) { + StringBuffer sb = new StringBuffer(); + sb.append("<"); //$NON-NLS-1$ + sb.append(name); + if (parameters != null) + for (Map.Entry entry : parameters.entrySet()) { + sb.append(" "); //$NON-NLS-1$ + String key = entry.getKey(); + sb.append(key); + sb.append("=\""); //$NON-NLS-1$ + sb.append(getEscaped(String.valueOf(entry.getValue()))); + sb.append("\""); //$NON-NLS-1$ + } + sb.append(">"); //$NON-NLS-1$ + if (shouldTab) + printTabulation(); + if (newLine) + println(sb.toString()); + else + print(sb.toString()); + } + + public void startTag(String name, HashMap parameters) { + startTag(name, parameters, true); + } + + public void startTag(String name, HashMap parameters, boolean newLine) { + printTag(name, parameters, true, newLine); + tab++; + } + + private static void appendEscapedChar(StringBuffer buffer, char c) { + String replacement = getReplacement(c); + if (replacement != null) { + buffer.append('&'); + buffer.append(replacement); + buffer.append(';'); + } else { + buffer.append(c); + } + } + + public static String getEscaped(String s) { + StringBuffer result = new StringBuffer(s.length() + 10); + for (int i = 0; i < s.length(); ++i) + appendEscapedChar(result, s.charAt(i)); + return result.toString(); + } + + private static String getReplacement(char c) { + // Encode special XML characters into the equivalent character references. + // These five are defined by default for all XML documents. + switch (c) { + case '<' : + return "lt"; //$NON-NLS-1$ + case '>' : + return "gt"; //$NON-NLS-1$ + case '"' : + return "quot"; //$NON-NLS-1$ + case '\'' : + return "apos"; //$NON-NLS-1$ + case '&' : + return "amp"; //$NON-NLS-1$ + } + return null; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java new file mode 100644 index 0000000000..f62736c41d --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2006, 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; + +/** + * A description of the changes found in a delta + */ +public class ChangeDescription { + + private List addedRoots = new ArrayList<>(); + private List changedFiles = new ArrayList<>(); + private List closedProjects = new ArrayList<>(); + private List copiedRoots = new ArrayList<>(); + private List movedRoots = new ArrayList<>(); + private List removedRoots = new ArrayList<>(); + + private IResource createSourceResource(IResourceDelta delta) { + IPath sourcePath = delta.getMovedFromPath(); + IResource resource = delta.getResource(); + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); + switch (resource.getType()) { + case IResource.PROJECT : + return wsRoot.getProject(sourcePath.segment(0)); + case IResource.FOLDER : + return wsRoot.getFolder(sourcePath); + case IResource.FILE : + return wsRoot.getFile(sourcePath); + } + return null; + } + + private void ensureResourceCovered(IResource resource, List list) { + IPath path = resource.getFullPath(); + for (Iterator iter = list.iterator(); iter.hasNext();) { + IResource root = iter.next(); + if (root.getFullPath().isPrefixOf(path)) { + return; + } + } + list.add(resource); + } + + public IResource[] getRootResources() { + Set result = new HashSet<>(); + result.addAll(addedRoots); + result.addAll(changedFiles); + result.addAll(closedProjects); + result.addAll(copiedRoots); + result.addAll(movedRoots); + result.addAll(removedRoots); + return result.toArray(new IResource[result.size()]); + } + + private void handleAdded(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) { + handleMove(delta); + } else if ((delta.getFlags() & IResourceDelta.COPIED_FROM) != 0) { + handleCopy(delta); + } else { + ensureResourceCovered(delta.getResource(), addedRoots); + } + } + + private void handleChange(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.REPLACED) != 0) { + // A replace was added in place of a removed resource + handleAdded(delta); + } else if (delta.getResource().getType() == IResource.FILE) { + ensureResourceCovered(delta.getResource(), changedFiles); + } + } + + private void handleCopy(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.COPIED_FROM) != 0) { + IResource source = createSourceResource(delta); + ensureResourceCovered(source, copiedRoots); + } + } + + private void handleMove(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) { + movedRoots.add(delta.getResource()); + } else if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) { + IResource source = createSourceResource(delta); + ensureResourceCovered(source, movedRoots); + } + } + + private void handleRemoved(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.OPEN) != 0) { + closedProjects.add(delta.getResource()); + } else if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) { + handleMove(delta); + } else { + ensureResourceCovered(delta.getResource(), removedRoots); + } + } + + /** + * Record the change and return whether any child changes should be visited. + * @param delta the change + * @return whether any child changes should be visited + */ + public boolean recordChange(IResourceDelta delta) { + switch (delta.getKind()) { + case IResourceDelta.ADDED : + handleAdded(delta); + return true; // Need to traverse children to look for moves or other changes under added roots + case IResourceDelta.REMOVED : + handleRemoved(delta); + // No need to look for further changes under a remove (such as moves). + // Changes will be discovered in corresponding destination delta + return false; + case IResourceDelta.CHANGED : + handleChange(delta); + return true; + } + return true; + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java new file mode 100644 index 0000000000..7f5577f9a7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.*; +import org.eclipse.core.expressions.*; +import org.eclipse.core.internal.resources.ResourceException; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +public class ModelProviderDescriptor implements IModelProviderDescriptor { + + private String id; + private String[] extendedModels; + private String label; + private ModelProvider provider; + private Expression enablementRule; + + private static EvaluationContext createEvaluationContext(Object element) { + EvaluationContext result = new EvaluationContext(null, element); + return result; + } + + public ModelProviderDescriptor(IExtension extension) throws CoreException { + readExtension(extension); + } + + private boolean convert(EvaluationResult eval) { + if (eval == EvaluationResult.FALSE) + return false; + return true; + } + + protected void fail(String reason) throws CoreException { + throw new ResourceException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, reason, null)); + } + + @Override + public String[] getExtendedModels() { + return extendedModels; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public IResource[] getMatchingResources(IResource[] resources) throws CoreException { + Set result = new HashSet<>(); + for (int i = 0; i < resources.length; i++) { + IResource resource = resources[i]; + EvaluationContext evalContext = createEvaluationContext(resource); + if (matches(evalContext)) { + result.add(resource); + } + } + return result.toArray(new IResource[result.size()]); + } + + @Override + public synchronized ModelProvider getModelProvider() throws CoreException { + if (provider == null) { + IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MODEL_PROVIDERS, id); + IConfigurationElement[] elements = extension.getConfigurationElements(); + for (int i = 0; i < elements.length; i++) { + IConfigurationElement element = elements[i]; + if (element.getName().equalsIgnoreCase("modelProvider")) { //$NON-NLS-1$ + try { + provider = (ModelProvider) element.createExecutableExtension("class"); //$NON-NLS-1$ + provider.init(this); + } catch (ClassCastException e) { + String message = NLS.bind(Messages.mapping_wrongType, id); + throw new CoreException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, Platform.PLUGIN_ERROR, message, e)); + } + } + } + } + return provider; + } + + public boolean matches(IEvaluationContext context) throws CoreException { + if (enablementRule == null) + return false; + return convert(enablementRule.evaluate(context)); + } + + /** + * Initialize this descriptor based on the provided extension point. + */ + protected void readExtension(IExtension extension) throws CoreException { + //read the extension + id = extension.getUniqueIdentifier(); + if (id == null) + fail(Messages.mapping_noIdentifier); + label = extension.getLabel(); + IConfigurationElement[] elements = extension.getConfigurationElements(); + int count = elements.length; + ArrayList extendsList = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + IConfigurationElement element = elements[i]; + String name = element.getName(); + if (name.equalsIgnoreCase("extends-model")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(NLS.bind(Messages.mapping_invalidDef, id)); + extendsList.add(attribute); + } else if (name.equalsIgnoreCase(ExpressionTagNames.ENABLEMENT)) { + enablementRule = ExpressionConverter.getDefault().perform(element); + } + } + extendedModels = extendsList.toArray(new String[extendsList.size()]); + } + + @Override + public ResourceTraversal[] getMatchingTraversals(ResourceTraversal[] traversals) throws CoreException { + List result = new ArrayList<>(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + if (getMatchingResources(traversal.getResources()).length > 0) { + result.add(traversal); + } + } + return result.toArray(new ResourceTraversal[result.size()]); + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java new file mode 100644 index 0000000000..01403a8e71 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.HashMap; +import java.util.Map; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.mapping.IModelProviderDescriptor; +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.runtime.*; + +public class ModelProviderManager { + + private static Map descriptors; + private static ModelProviderManager instance; + + public synchronized static ModelProviderManager getDefault() { + if (instance == null) { + instance = new ModelProviderManager(); + } + return instance; + } + + private void detectCycles() { + // TODO Auto-generated method stub + + } + + public IModelProviderDescriptor getDescriptor(String id) { + lazyInitialize(); + return descriptors.get(id); + } + + public IModelProviderDescriptor[] getDescriptors() { + lazyInitialize(); + return descriptors.values().toArray(new IModelProviderDescriptor[descriptors.size()]); + } + + public ModelProvider getModelProvider(String modelProviderId) throws CoreException { + IModelProviderDescriptor desc = getDescriptor(modelProviderId); + if (desc == null) + return null; + return desc.getModelProvider(); + } + + protected void lazyInitialize() { + if (descriptors != null) + return; + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MODEL_PROVIDERS); + IExtension[] extensions = point.getExtensions(); + descriptors = new HashMap<>(extensions.length * 2 + 1); + for (int i = 0, imax = extensions.length; i < imax; i++) { + IModelProviderDescriptor desc = null; + try { + desc = new ModelProviderDescriptor(extensions[i]); + } catch (CoreException e) { + Policy.log(e); + } + if (desc != null) + descriptors.put(desc.getId(), desc); + } + //do cycle detection now so it only has to be done once + //cycle detection on a graph subset is a pain + detectCycles(); + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java new file mode 100644 index 0000000000..e3e3bc494f --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright (c) 2006, 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * Concrete implementation of IResourceDelta used for operation validation + */ +public final class ProposedResourceDelta extends PlatformObject implements IResourceDelta { + protected static int KIND_MASK = 0xFF; + + private HashMap children = new HashMap<>(8); + private IPath movedFromPath; + private IPath movedToPath; + private IResource resource; + private int status; + + public ProposedResourceDelta(IResource resource) { + this.resource = resource; + } + + @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 { + if (!visitor.visit(this)) + return; + for (Iterator iter = children.values().iterator(); iter.hasNext();) { + ProposedResourceDelta childDelta = iter.next(); + childDelta.accept(visitor, memberFlags); + } + } + + /** + * Adds a child delta to the list of children for this delta node. + * @param delta + */ + protected void add(ProposedResourceDelta delta) { + if (children.size() == 0 && status == 0) + setKind(IResourceDelta.CHANGED); + children.put(delta.getResource().getName(), delta); + } + + /** + * Adds the given flags to this delta. + * @param flags The flags to add + */ + protected void addFlags(int flags) { + //make sure the provided flags don't influence the kind + this.status |= (flags & ~KIND_MASK); + } + + @Override + public IResourceDelta findMember(IPath path) { + int segmentCount = path.segmentCount(); + if (segmentCount == 0) + return this; + + //iterate over the path and find matching child delta + ProposedResourceDelta current = this; + for (int i = 0; i < segmentCount; i++) { + current = current.children.get(path.segment(i)); + if (current == null) + return null; + } + return current; + } + + @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) { + List result = new ArrayList<>(); + for (Iterator iter = children.values().iterator(); iter.hasNext();) { + ProposedResourceDelta child = iter.next(); + if ((child.getKind() & kindMask) != 0) + result.add(child); + } + return result.toArray(new IResourceDelta[result.size()]); + } + + /** + * Returns the child delta corresponding to the given child resource name, + * or null. + */ + ProposedResourceDelta getChild(String name) { + return children.get(name); + } + + @Override + public int getFlags() { + return status & ~KIND_MASK; + } + + @Override + public IPath getFullPath() { + return getResource().getFullPath(); + } + + @Override + public int getKind() { + return status & KIND_MASK; + } + + @Override + public IMarkerDelta[] getMarkerDeltas() { + return new IMarkerDelta[0]; + } + + @Override + public IPath getMovedFromPath() { + return movedFromPath; + } + + @Override + public IPath getMovedToPath() { + return movedToPath; + } + + @Override + public IPath getProjectRelativePath() { + return getResource().getProjectRelativePath(); + } + + @Override + public IResource getResource() { + return resource; + } + + public void setFlags(int flags) { + status = getKind() | (flags & ~KIND_MASK); + } + + protected void setKind(int kind) { + status = getFlags() | (kind & KIND_MASK); + } + + protected void setMovedFromPath(IPath path) { + movedFromPath = path; + } + + protected void setMovedToPath(IPath path) { + movedToPath = path; + } + + /** + * For debugging purposes only. + */ + @Override + public String toString() { + return "ProposedDelta(" + resource + ')'; //$NON-NLS-1$ + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java new file mode 100644 index 0000000000..03c0ee77f1 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * 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.resources.mapping; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.runtime.IAdapterFactory; + +/** + * Adapter factory converting IResource to ResourceMapping + * + * @since 3.1 + */ +public class ResourceAdapterFactory implements IAdapterFactory { + @Override + @SuppressWarnings("unchecked") + public T getAdapter(Object adaptableObject, Class adapterType) { + if (adapterType == ResourceMapping.class && adaptableObject instanceof IResource) { + return (T) new SimpleResourceMapping((IResource) adaptableObject); + } + return null; + } + + @Override + public Class[] getAdapterList() { + return new Class[] {ResourceMapping.class}; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java new file mode 100644 index 0000000000..bbdfa5ee42 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java @@ -0,0 +1,240 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.mapping; + +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; + +/** + * Factory for creating a resource delta that describes a proposed change. + */ +public class ResourceChangeDescriptionFactory implements IResourceChangeDescriptionFactory { + private ProposedResourceDelta root = new ProposedResourceDelta(ResourcesPlugin.getWorkspace().getRoot()); + + /** + * Creates and a delta representing a deleted resource, and adds it to the provided + * parent delta. + * @param parentDelta The parent of the deletion delta to create + * @param resource The deleted resource to create a delta for + */ + private ProposedResourceDelta buildDeleteDelta(ProposedResourceDelta parentDelta, IResource resource) { + //start with the existing delta for this resource, if any, to preserve other flags + ProposedResourceDelta delta = parentDelta.getChild(resource.getName()); + if (delta == null) { + delta = new ProposedResourceDelta(resource); + parentDelta.add(delta); + } + delta.setKind(IResourceDelta.REMOVED); + if (resource.getType() == IResource.FILE) + return delta; + //recurse to build deletion deltas for children + try { + IResource[] members = ((IContainer) resource).members(); + int childCount = members.length; + if (childCount > 0) { + ProposedResourceDelta[] childDeltas = new ProposedResourceDelta[childCount]; + for (int i = 0; i < childCount; i++) + childDeltas[i] = buildDeleteDelta(delta, members[i]); + } + } catch (CoreException e) { + //don't need to create deletion deltas for children of inaccessible resources + } + return delta; + } + + @Override + public void change(IFile file) { + ProposedResourceDelta delta = getDelta(file); + if (delta.getKind() == 0) + delta.setKind(IResourceDelta.CHANGED); + //the CONTENT flag only applies to the changed and moved from cases + if (delta.getKind() == IResourceDelta.CHANGED || (delta.getFlags() & IResourceDelta.MOVED_FROM) != 0 || (delta.getFlags() & IResourceDelta.COPIED_FROM) != 0) + delta.addFlags(IResourceDelta.CONTENT); + } + + @Override + public void close(IProject project) { + delete(project); + ProposedResourceDelta delta = getDelta(project); + delta.addFlags(IResourceDelta.OPEN); + } + + @Override + public void copy(IResource resource, IPath destination) { + moveOrCopyDeep(resource, destination, false /* copy */); + } + + @Override + public void create(IResource resource) { + getDelta(resource).setKind(IResourceDelta.ADDED); + } + + @Override + public void delete(IResource resource) { + if (resource.getType() == IResource.ROOT) { + //the root itself cannot be deleted, so create deletions for each project + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + buildDeleteDelta(root, projects[i]); + } else { + buildDeleteDelta(getDelta(resource.getParent()), resource); + } + } + + private void fail(CoreException e) { + Policy.log(e.getStatus().getSeverity(), "An internal error occurred while accumulating a change description.", e); //$NON-NLS-1$ + } + + @Override + public IResourceDelta getDelta() { + return root; + } + + ProposedResourceDelta getDelta(IResource resource) { + ProposedResourceDelta delta = (ProposedResourceDelta) root.findMember(resource.getFullPath()); + if (delta != null) { + return delta; + } + ProposedResourceDelta parent = getDelta(resource.getParent()); + delta = new ProposedResourceDelta(resource); + parent.add(delta); + return delta; + } + + /* + * Return the resource at the destination path that corresponds to the source resource + * @param source the source resource + * @param sourcePrefix the path of the root of the move or copy + * @param destinationPrefix the path of the destination the root was copied to + * @return the destination resource + */ + protected IResource getDestinationResource(IResource source, IPath sourcePrefix, IPath destinationPrefix) { + IPath relativePath = source.getFullPath().removeFirstSegments(sourcePrefix.segmentCount()); + IPath destinationPath = destinationPrefix.append(relativePath); + IResource destination; + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); + switch (source.getType()) { + case IResource.FILE : + destination = wsRoot.getFile(destinationPath); + break; + case IResource.FOLDER : + destination = wsRoot.getFolder(destinationPath); + break; + case IResource.PROJECT : + destination = wsRoot.getProject(destinationPath.segment(0)); + break; + default : + // Shouldn't happen + destination = null; + } + return destination; + } + + @Override + public void move(IResource resource, IPath destination) { + moveOrCopyDeep(resource, destination, true /* move */); + } + + /** + * Builds the delta representing a single resource being moved or copied. + * + * @param resource The resource being moved + * @param sourcePrefix The root of the sub-tree being moved + * @param destinationPrefix The root of the destination sub-tree + * @param move true for a move, false for a copy + * @return Whether to move or copy the child + */ + boolean moveOrCopy(IResource resource, final IPath sourcePrefix, final IPath destinationPrefix, final boolean move) { + ProposedResourceDelta sourceDelta = getDelta(resource); + if (sourceDelta.getKind() == IResourceDelta.REMOVED) { + // There is already a removed delta here so there + // is nothing to move/copy + return false; + } + IResource destinationResource = getDestinationResource(resource, sourcePrefix, destinationPrefix); + ProposedResourceDelta destinationDelta = getDelta(destinationResource); + if ((destinationDelta.getKind() & (IResourceDelta.ADDED | IResourceDelta.CHANGED)) > 0) { + // There is already a resource at the destination + // TODO: What do we do + return false; + } + // First, create the delta for the source + IPath fromPath = resource.getFullPath(); + boolean wasAdded = false; + final int sourceFlags = sourceDelta.getFlags(); + if (move) { + // We transfer the source flags to the destination + if (sourceDelta.getKind() == IResourceDelta.ADDED) { + if ((sourceFlags & IResourceDelta.MOVED_FROM) != 0) { + // The resource was moved from somewhere else so + // we need to transfer the path to the new location + fromPath = sourceDelta.getMovedFromPath(); + sourceDelta.setMovedFromPath(null); + } + // The source was added and then moved so we'll + // make it an add at the destination + sourceDelta.setKind(0); + wasAdded = true; + } else { + // We reset the status to be a remove/move_to + sourceDelta.setKind(IResourceDelta.REMOVED); + sourceDelta.setFlags(IResourceDelta.MOVED_TO); + sourceDelta.setMovedToPath(destinationPrefix.append(fromPath.removeFirstSegments(sourcePrefix.segmentCount()))); + } + } + // Next, create the delta for the destination + if (destinationDelta.getKind() == IResourceDelta.REMOVED) { + // The destination was removed and is being re-added + destinationDelta.setKind(IResourceDelta.CHANGED); + destinationDelta.addFlags(IResourceDelta.REPLACED); + } else { + destinationDelta.setKind(IResourceDelta.ADDED); + } + if (!wasAdded || !fromPath.equals(resource.getFullPath())) { + // The source wasn't added so it is a move/copy + destinationDelta.addFlags(move ? IResourceDelta.MOVED_FROM : IResourceDelta.COPIED_FROM); + destinationDelta.setMovedFromPath(fromPath); + // Apply the source flags + if (move) + destinationDelta.addFlags(sourceFlags); + } + + return true; + } + + /** + * Helper method that generate a move or copy delta for a sub-tree + * of resources being moved or copied. + */ + private void moveOrCopyDeep(IResource resource, IPath destination, final boolean move) { + final IPath sourcePrefix = resource.getFullPath(); + final IPath destinationPrefix = destination; + try { + //build delta for the entire sub-tree if available + if (resource.isAccessible()) { + resource.accept(new IResourceVisitor() { + @Override + public boolean visit(IResource child) { + return moveOrCopy(child, sourcePrefix, destinationPrefix, move); + } + }); + } else { + //just build a delta for the single resource + moveOrCopy(resource, sourcePrefix, destination, move); + } + } catch (CoreException e) { + fail(e); + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java new file mode 100644 index 0000000000..771e9f56b7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.*; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A simple model provider that represents the resource model itself. + * + * @since 3.2 + */ +public final class ResourceModelProvider extends ModelProvider { + + @Override + public ResourceMapping[] getMappings(IResource resource, ResourceMappingContext context, IProgressMonitor monitor) { + return new ResourceMapping[] {new SimpleResourceMapping(resource)}; + } + + @Override + public ResourceMapping[] getMappings(ResourceTraversal[] traversals, ResourceMappingContext context, IProgressMonitor monitor) { + Set result = new HashSet<>(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + IResource[] resources = traversal.getResources(); + int depth = traversal.getDepth(); + for (int j = 0; j < resources.length; j++) { + IResource resource = resources[j]; + switch (depth) { + case IResource.DEPTH_INFINITE : + result.add(resource); + break; + case IResource.DEPTH_ONE : + if (resource.getType() == IResource.FILE) { + result.add(resource); + } else { + result.add(new ShallowContainer((IContainer) resource)); + } + break; + case IResource.DEPTH_ZERO : + if (resource.getType() == IResource.FILE) + result.add(resource); + break; + } + } + } + ResourceMapping[] mappings = new ResourceMapping[result.size()]; + int i = 0; + for (Iterator iter = result.iterator(); iter.hasNext();) { + Object element = iter.next(); + if (element instanceof IResource) { + mappings[i++] = new SimpleResourceMapping((IResource) element); + } else { + mappings[i++] = new ShallowResourceMapping((ShallowContainer) element); + } + } + return mappings; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java new file mode 100644 index 0000000000..f26d56bc18 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.mapping; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.PlatformObject; + +/** + * A special model object used to represent shallow folders + */ +public class ShallowContainer extends PlatformObject { + + private IContainer container; + + public ShallowContainer(IContainer container) { + this.container = container; + } + + public IContainer getResource() { + return container; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj instanceof ShallowContainer) { + ShallowContainer other = (ShallowContainer) obj; + return other.getResource().equals(getResource()); + } + return false; + } + + @Override + public int hashCode() { + return getResource().hashCode(); + } + + @Override + @SuppressWarnings("unchecked") + public T getAdapter(Class adapter) { + if (adapter == IResource.class || adapter == IContainer.class) + return (T) container; + return super.getAdapter(adapter); + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java new file mode 100644 index 0000000000..54421f0f3f --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.mapping; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A resource mapping for a shallow container. + */ +public class ShallowResourceMapping extends ResourceMapping { + + private final ShallowContainer container; + + public ShallowResourceMapping(ShallowContainer container) { + this.container = container; + } + + @Override + public Object getModelObject() { + return container; + } + + @Override + public String getModelProviderId() { + return ModelProvider.RESOURCE_MODEL_PROVIDER_ID; + } + + @Override + public IProject[] getProjects() { + return new IProject[] { container.getResource().getProject() }; + } + + @Override + public ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) { + return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { container.getResource() }, IResource.DEPTH_ONE, IResource.NONE)}; + } + + @Override + public boolean contains(ResourceMapping mapping) { + if (mapping.getModelProviderId().equals(this.getModelProviderId())) { + Object object = mapping.getModelObject(); + IResource resource = container.getResource(); + // A shallow mapping only contains direct file children or equal shallow containers + if (object instanceof ShallowContainer) { + ShallowContainer sc = (ShallowContainer) object; + return sc.getResource().equals(resource); + } + if (object instanceof IResource) { + IResource other = (IResource) object; + return other.getType() == IResource.FILE + && resource.getFullPath().equals(other.getFullPath().removeLastSegments(1)); + } + } + return false; + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java new file mode 100644 index 0000000000..94fa38b414 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * 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.resources.mapping; + +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A simple resource mapping for converting IResource to ResourceMapping. + * It uses the resource as the model object and traverses deeply. + * + * @since 3.1 + */ +public class SimpleResourceMapping extends ResourceMapping { + private final IResource resource; + + public SimpleResourceMapping(IResource resource) { + this.resource = resource; + } + + @Override + public boolean contains(ResourceMapping mapping) { + if (mapping.getModelProviderId().equals(this.getModelProviderId())) { + Object object = mapping.getModelObject(); + if (object instanceof IResource) { + IResource other = (IResource) object; + return resource.getFullPath().isPrefixOf(other.getFullPath()); + } + if (object instanceof ShallowContainer) { + ShallowContainer sc = (ShallowContainer) object; + IResource other = sc.getResource(); + return resource.getFullPath().isPrefixOf(other.getFullPath()); + } + } + return false; + } + + @Override + public Object getModelObject() { + return resource; + } + + @Override + public String getModelProviderId() { + return ModelProvider.RESOURCE_MODEL_PROVIDER_ID; + } + + @Override + public IProject[] getProjects() { + if (resource.getType() == IResource.ROOT) + return ((IWorkspaceRoot)resource).getProjects(); + return new IProject[] {resource.getProject()}; + } + + @Override + public ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) { + if (resource.getType() == IResource.ROOT) { + return new ResourceTraversal[] {new ResourceTraversal(((IWorkspaceRoot)resource).getProjects(), IResource.DEPTH_INFINITE, IResource.NONE)}; + } + return new ResourceTraversal[] {new ResourceTraversal(new IResource[] {resource}, IResource.DEPTH_INFINITE, IResource.NONE)}; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java new file mode 100644 index 0000000000..6c2a9646d3 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import java.net.URISyntaxException; +import java.net.URL; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.URIUtil; + +/** + * ECLIPSE_HOME project variable, pointing to the location of the eclipse install directory. + * + */ +public class EclipseHomeProjectVariable extends PathVariableResolver { + + public static String NAME = "ECLIPSE_HOME"; //$NON-NLS-1$ + + public EclipseHomeProjectVariable() { + // nothing to do. + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + URL installURL = Platform.getInstallLocation().getURL(); + try { + return URIUtil.toURI(installURL).toASCIIString(); + } catch (URISyntaxException e) { + return null; + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java new file mode 100644 index 0000000000..6ebb96a180 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import java.net.URI; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * Path Variable representing the parent directory of the variable provided + * in argument, following the syntax: + * + * "${PARENT-COUNT-MyVariable}" + * + */ +public class ParentVariableResolver extends PathVariableResolver { + + final public static String NAME = "PARENT"; //$NON-NLS-1$ + + public ParentVariableResolver() { + // nothing + } + + @Override + public String getValue(String variable, IResource resource) { + int index = variable.indexOf('-'); + if (index == -1 || index == (variable.length() -1)) + return null; + + String countRemaining = variable.substring(index + 1); + index = countRemaining.indexOf('-'); + if (index == -1 || index == (variable.length() -1)) + return null; + + String countString = countRemaining.substring(0, index); + int count = 0; + try { + count = Integer.parseInt(countString); + if (count < 0) + return null; + }catch (NumberFormatException e) { + return null; + } + String argument = countRemaining.substring(index + 1); + + URI value = resource.getPathVariableManager().getURIValue(argument); + if (value == null) + return null; + value = resource.getPathVariableManager().resolveURI(value); + value = URIUtil.toURI(URIUtil.toPath(value).removeLastSegments(count)); + + return value.toASCIIString(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java new file mode 100644 index 0000000000..a9c232fdb4 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * + */ +public class ProjectLocationVariableResolver extends PathVariableResolver { + + public static String NAME = "PROJECT_LOC"; //$NON-NLS-1$ + + public ProjectLocationVariableResolver() { + // nothing + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + if (resource.getProject().getLocationURI() != null) + return resource.getProject().getLocationURI().toASCIIString(); + return null; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java new file mode 100644 index 0000000000..81cefeb12c --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * + */ +public class WorkspaceLocationVariableResolver extends PathVariableResolver { + + public static String NAME = "WORKSPACE_LOC"; //$NON-NLS-1$ + + public WorkspaceLocationVariableResolver() { + // nothing to do + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + return resource.getWorkspace().getRoot().getLocationURI().toASCIIString(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java new file mode 100644 index 0000000000..71c6bbf176 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import java.net.URI; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * Returns the location of the parent resource + */ +public class WorkspaceParentLocationVariableResolver extends PathVariableResolver { + + public static String NAME = "PARENT_LOC"; //$NON-NLS-1$ + + public WorkspaceParentLocationVariableResolver() { + // nothing + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + IContainer parent = resource.getParent(); + if (parent != null) { + URI locationURI = parent.getLocationURI(); + if (locationURI != null) + return locationURI.toASCIIString(); + } + return null; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java new file mode 100644 index 0000000000..84554589b6 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2002, 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 - Initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.resources.refresh.win32; + +import java.io.*; + +/** + * Performs character to byte conversion for passing strings to native win32 + * methods. + */ +public class Convert { + + /* + * Obtains the default encoding on this platform + */ + private static String defaultEncoding= + new InputStreamReader(new ByteArrayInputStream(new byte[0])).getEncoding(); + + /** + * Converts the given String to bytes using the platforms default + * encoding. + * + * @param target The String to be converted, can not be null. + * @return byte[] The resulting bytes, or null. + */ + /** + * Calling String.getBytes() creates a new encoding object and other garbage. + * This can be avoided by calling String.getBytes(String encoding) instead. + */ + public static byte[] toPlatformBytes(String target) { + if (defaultEncoding == null) + return target.getBytes(); + // try to use the default encoding + try { + return target.getBytes(defaultEncoding); + } catch (UnsupportedEncodingException e) { + // null the default encoding so we don't try it again + defaultEncoding = null; + return target.getBytes(); + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java new file mode 100644 index 0000000000..4db528d25a --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java @@ -0,0 +1,603 @@ +/******************************************************************************* + * Copyright (c) 2002, 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 - Initial API and implementation + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources.refresh.win32; + +import java.io.File; +import java.util.*; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.refresh.IRefreshMonitor; +import org.eclipse.core.resources.refresh.IRefreshResult; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.Bundle; + +/** + * A monitor that works on Win32 platforms. Provides simple notification of + * entire trees by reporting that the root of the tree has changed to depth + * DEPTH_INFINITE. + */ +class Win32Monitor extends Job implements IRefreshMonitor { + /** + * The delay between invocations of the refresh job. + */ + private static final long RESCHEDULE_DELAY = 3000; + /** + * The time to wait on blocking call to native refresh hook. + */ + private static final int WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT = 1000; + private static final String DEBUG_PREFIX = "Win32RefreshMonitor: "; //$NON-NLS-1$ + + /** + * A ChainedHandle is a linked list of handles. + */ + protected abstract class ChainedHandle extends Handle { + private ChainedHandle next; + private ChainedHandle previous; + + public abstract boolean exists(); + + public ChainedHandle getNext() { + return next; + } + + public ChainedHandle getPrevious() { + return previous; + } + + public void setNext(ChainedHandle next) { + this.next = next; + } + + public void setPrevious(ChainedHandle previous) { + this.previous = previous; + } + } + + protected class FileHandle extends ChainedHandle { + private File file; + + public FileHandle(File file) { + this.file = file; + } + + @Override + public boolean exists() { + return file.exists(); + } + + @Override + public void handleNotification() { + if (!isOpen()) + return; + ChainedHandle next = getNext(); + if (next != null) { + if (next.isOpen()) { + if (!next.exists()) { + if (next instanceof LinkedResourceHandle) { + next.close(); + LinkedResourceHandle linkedResourceHandle = (LinkedResourceHandle) next; + linkedResourceHandle.postRefreshRequest(); + } else { + next.close(); + } + ChainedHandle previous = getPrevious(); + if (previous != null) + previous.open(); + } + } else { + next.open(); + if (next.isOpen()) { + Handle previous = getPrevious(); + previous.close(); + if (next instanceof LinkedResourceHandle) + ((LinkedResourceHandle) next).postRefreshRequest(); + } + } + } + findNextChange(); + } + + @Override + public void open() { + if (!isOpen()) { + Handle next = getNext(); + if (next != null && next.isOpen()) { + openHandleOn(file); + } else { + if (exists()) { + openHandleOn(file); + } + Handle previous = getPrevious(); + if (previous != null) { + previous.open(); + } + } + } + } + } + + protected abstract class Handle { + protected long handleValue; + + public Handle() { + handleValue = Win32Natives.INVALID_HANDLE_VALUE; + } + + public void close() { + if (isOpen()) { + if (!Win32Natives.FindCloseChangeNotification(handleValue)) { + int error = Win32Natives.GetLastError(); + if (error != Win32Natives.ERROR_INVALID_HANDLE) + addException(NLS.bind(Messages.WM_errCloseHandle, Integer.toString(error))); + } + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "removed handle: " + handleValue); //$NON-NLS-1$ + handleValue = Win32Natives.INVALID_HANDLE_VALUE; + } + } + + private long createHandleValue(String path, boolean monitorSubtree, int flags) { + long handle = Win32Natives.FindFirstChangeNotification(path, monitorSubtree, flags); + if (handle == Win32Natives.INVALID_HANDLE_VALUE) { + int error = Win32Natives.GetLastError(); + addException(NLS.bind(Messages.WM_errCreateHandle, path, Integer.toString(error))); + } + return handle; + } + + public void destroy() { + close(); + } + + protected void findNextChange() { + if (!Win32Natives.FindNextChangeNotification(handleValue)) { + int error = Win32Natives.GetLastError(); + if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) { + addException(NLS.bind(Messages.WM_errFindChange, Integer.toString(error))); + } + removeHandle(this); + } + } + + public long getHandleValue() { + return handleValue; + } + + public abstract void handleNotification(); + + public boolean isOpen() { + return handleValue != Win32Natives.INVALID_HANDLE_VALUE; + } + + public abstract void open(); + + protected void openHandleOn(File file) { + openHandleOn(file.getAbsolutePath(), false); + } + + protected void openHandleOn(IResource resource) { + openHandleOn(resource.getLocation().toOSString(), true); + } + + private void openHandleOn(String path, boolean subtree) { + setHandleValue(createHandleValue(path, subtree, Win32Natives.FILE_NOTIFY_CHANGE_FILE_NAME | Win32Natives.FILE_NOTIFY_CHANGE_DIR_NAME | Win32Natives.FILE_NOTIFY_CHANGE_LAST_WRITE | Win32Natives.FILE_NOTIFY_CHANGE_SIZE)); + if (isOpen()) { + fHandleValueToHandle.put(getHandleValue(), this); + setHandleValueArrays(createHandleArrays()); + } else { + close(); + } + } + + protected void postRefreshRequest(IResource resource) { + //native callback occurs even if resource was changed within workspace + if (!resource.isSynchronized(IResource.DEPTH_INFINITE)) + refreshResult.refresh(resource); + } + + public void setHandleValue(long handleValue) { + this.handleValue = handleValue; + } + } + + protected class LinkedResourceHandle extends ChainedHandle { + private List fileHandleChain; + private IResource resource; + + /** + * @param resource + */ + public LinkedResourceHandle(IResource resource) { + this.resource = resource; + createFileHandleChain(); + } + + protected void createFileHandleChain() { + fileHandleChain = new ArrayList<>(1); + File file = new File(resource.getLocation().toOSString()); + file = file.getParentFile(); + while (file != null) { + fileHandleChain.add(0, new FileHandle(file)); + file = file.getParentFile(); + } + int size = fileHandleChain.size(); + for (int i = 0; i < size; i++) { + ChainedHandle handle = fileHandleChain.get(i); + handle.setPrevious((i > 0) ? fileHandleChain.get(i - 1) : null); + handle.setNext((i + 1 < size) ? fileHandleChain.get(i + 1) : this); + } + setPrevious((size > 0) ? fileHandleChain.get(size - 1) : null); + } + + @Override + public void destroy() { + super.destroy(); + for (Iterator i = fileHandleChain.iterator(); i.hasNext();) { + Handle handle = i.next(); + handle.destroy(); + } + } + + @Override + public boolean exists() { + IPath location = resource.getLocation(); + return location == null ? false : location.toFile().exists(); + } + + @Override + public void handleNotification() { + if (isOpen()) { + postRefreshRequest(resource); + findNextChange(); + } + } + + @Override + public void open() { + if (!isOpen()) { + if (exists()) { + openHandleOn(resource); + } + FileHandle handle = (FileHandle) getPrevious(); + if (handle != null && !handle.isOpen()) { + handle.open(); + } + } + } + + public void postRefreshRequest() { + postRefreshRequest(resource); + } + } + + protected class ResourceHandle extends Handle { + private IResource resource; + + public ResourceHandle(IResource resource) { + super(); + this.resource = resource; + } + + public IResource getResource() { + return resource; + } + + @Override + public void handleNotification() { + if (isOpen()) { + postRefreshRequest(resource); + findNextChange(); + } + } + + @Override + public void open() { + if (!isOpen()) { + openHandleOn(resource); + } + } + } + + /** + * Any errors that have occurred + */ + protected MultiStatus errors; + /** + * Arrays of handles, split evenly when the number of handles is larger + * than Win32Natives.MAXIMUM_WAIT_OBJECTS + */ + protected long[][] fHandleValueArrays; + /** + * Mapping of handles (java.lang.Long) to absolute paths + * (java.lang.String). + */ + protected Map fHandleValueToHandle; + protected IRefreshResult refreshResult; + + /* + * Creates a new monitor. @param result A result that will receive refresh + * callbacks and error notifications + */ + public Win32Monitor(IRefreshResult result) { + super(Messages.WM_jobName); + this.refreshResult = result; + setPriority(Job.DECORATE); + setSystem(true); + fHandleValueToHandle = new HashMap<>(1); + setHandleValueArrays(createHandleArrays()); + } + + /** + * Logs an exception + */ + protected synchronized void addException(String message) { + if (errors == null) { + String msg = Messages.WM_errors; + errors = new MultiStatus(ResourcesPlugin.PI_RESOURCES, 1, msg, null); + } + errors.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, message, null)); + } + + /* + * Splits the given array into arrays of length no greater than max + * . The lengths of the sub arrays differ in size by no more than + * one element.

          Examples:

          • If an array of size 11 is split + * with a max of 4, the resulting arrays are of size 4, 4, and 3.
          • + *
          • If an array of size 18 is split with a max of 5, the resulting + * arrays are of size 5, 5, 4, and 4.
          + */ + private long[][] balancedSplit(final long[] array, final int max) { + int elementCount = array.length; + // want to handle [1, max] rather than [0, max) + int subArrayCount = ((elementCount - 1) / max) + 1; + int subArrayBaseLength = elementCount / subArrayCount; + int overflow = elementCount % subArrayCount; + long[][] result = new long[subArrayCount][]; + int count = 0; + for (int i = 0; i < subArrayCount; i++) { + int subArrayLength = subArrayBaseLength + (overflow-- > 0 ? 1 : 0); + long[] subArray = new long[subArrayLength]; + for (int j = 0; j < subArrayLength; j++) { + subArray[j] = array[count++]; + } + result[i] = subArray; + } + return result; + } + + private Handle createHandle(IResource resource) { + if (resource.isLinked()) + return new LinkedResourceHandle(resource); + return new ResourceHandle(resource); + } + + /* + * Since the Win32Natives.WaitForMultipleObjects(...) method cannot accept + * more than a certain number of objects, we are forced to split the array + * of objects to monitor and monitor each one individually.

          This method + * splits the list of handles into arrays no larger than + * Win32Natives.MAXIMUM_WAIT_OBJECTS. The arrays are balanced so that they + * differ in size by no more than one element. + */ + protected long[][] createHandleArrays() { + long[] handles; + // synchronized: in order to protect the map during iteration + synchronized (fHandleValueToHandle) { + Set keys = fHandleValueToHandle.keySet(); + int size = keys.size(); + if (size == 0) { + return new long[0][0]; + } + handles = new long[size]; + int count = 0; + for (Iterator i = keys.iterator(); i.hasNext();) { + handles[count++] = i.next().longValue(); + } + } + return balancedSplit(handles, Win32Natives.MAXIMUM_WAIT_OBJECTS); + } + + private Handle getHandle(IResource resource) { + if (resource == null) { + return null; + } + // synchronized: in order to protect the map during iteration + synchronized (fHandleValueToHandle) { + for (Iterator i = fHandleValueToHandle.values().iterator(); i.hasNext();) { + Handle handle = i.next(); + if (handle instanceof ResourceHandle) { + ResourceHandle resourceHandle = (ResourceHandle) handle; + if (resourceHandle.getResource().equals(resource)) { + return handle; + } + } + } + } + return null; + } + + /* + * Answers arrays of handles. The handles are split evenly when the number + * of handles becomes larger than Win32Natives.MAXIMUM_WAIT_OBJECTS. + * @return long[][] + */ + private long[][] getHandleValueArrays() { + return fHandleValueArrays; + } + + /** + * Adds a resource to be monitored by this native monitor + */ + public boolean monitor(IResource resource) { + IPath location = resource.getLocation(); + if (location == null) { + // cannot monitor remotely managed containers + return false; + } + Handle handle = createHandle(resource); + // synchronized: handle creation must be atomic + synchronized (this) { + handle.open(); + } + if (!handle.isOpen()) { + //ignore errors if we can't even create a handle on the resource + //it will fall back to polling anyway + errors = null; + return false; + } + //make sure the job is running + schedule(RESCHEDULE_DELAY); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + " added monitor for: " + resource); //$NON-NLS-1$ + return true; + } + + /** + * Removes the handle from the fHandleValueToHandle map. + * + * @param handle + * a handle, not null + */ + protected void removeHandle(Handle handle) { + List handles = new ArrayList<>(1); + handles.add(handle); + removeHandles(handles); + } + + /** + * Removes all of the handles in the given collection from the fHandleValueToHandle + * map. If collections from the fHandleValueToHandle map are + * used, copy them before passing them in as this method modifies the + * fHandleValueToHandle map. + * + * @param handles + * a collection of handles, not null + */ + private void removeHandles(Collection handles) { + // synchronized: protect the array, removal must be atomic + synchronized (this) { + for (Iterator i = handles.iterator(); i.hasNext();) { + Handle handle = i.next(); + fHandleValueToHandle.remove(handle.getHandleValue()); + handle.destroy(); + } + setHandleValueArrays(createHandleArrays()); + } + } + + /* + * @see java.lang.Runnable#run() + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + long start = -System.currentTimeMillis(); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "job started."); //$NON-NLS-1$ + try { + long[][] handleArrays = getHandleValueArrays(); + monitor.beginTask(Messages.WM_beginTask, handleArrays.length); + // If changes occur to the list of handles, + // ignore them until the next time through the loop. + for (int i = 0, length = handleArrays.length; i < length; i++) { + if (monitor.isCanceled()) + return Status.CANCEL_STATUS; + waitForNotification(handleArrays[i]); + monitor.worked(1); + } + } finally { + monitor.done(); + start += System.currentTimeMillis(); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "job finished in: " + start + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + //always reschedule the job - so it will come back after errors or cancelation + long delay = Math.max(RESCHEDULE_DELAY, start); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "rescheduling in: " + delay / 1000 + " seconds"); //$NON-NLS-1$ //$NON-NLS-2$ + final Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES); + //if the bundle is null then the framework has shutdown - just bail out completely (bug 98219) + if (bundle == null) + return Status.OK_STATUS; + //don't reschedule the job if the resources plugin has been shut down + if (bundle.getState() == Bundle.ACTIVE) + schedule(delay); + MultiStatus result = errors; + errors = null; + //just log native refresh failures + if (result != null && !result.isOK()) + ResourcesPlugin.getPlugin().getLog().log(result); + return Status.OK_STATUS; + } + + protected void setHandleValueArrays(long[][] arrays) { + fHandleValueArrays = arrays; + } + + @Override + public boolean shouldRun() { + return !fHandleValueToHandle.isEmpty(); + } + + @Override + public void unmonitor(IResource resource) { + if (resource == null) { + // resource == null means stop monitoring all resources + synchronized (fHandleValueToHandle) { + removeHandles(new ArrayList<>(fHandleValueToHandle.values())); + } + } else { + Handle handle = getHandle(resource); + if (handle != null) + removeHandle(handle); + } + //stop the job if there are no more handles + if (fHandleValueToHandle.isEmpty()) + cancel(); + } + + /** + * Performs the native call to wait for notification on one of the given + * handles. + * + * @param handleValues + * an array of handles, it must contain no duplicates. + */ + private void waitForNotification(long[] handleValues) { + int handleCount = handleValues.length; + int index = Win32Natives.WaitForMultipleObjects(handleCount, handleValues, false, WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT); + if (index == Win32Natives.WAIT_TIMEOUT) { + // nothing happened. + return; + } + if (index == Win32Natives.WAIT_FAILED) { + // we ran into a problem + int error = Win32Natives.GetLastError(); + if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) { + addException(NLS.bind(Messages.WM_nativeErr, Integer.toString(error))); + refreshResult.monitorFailed(this, null); + } + return; + } + // a change occurred + // WaitForMultipleObjects returns WAIT_OBJECT_0 + index + index -= Win32Natives.WAIT_OBJECT_0; + Handle handle = fHandleValueToHandle.get(handleValues[index]); + if (handle != null) + handle.handleNotification(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java new file mode 100644 index 0000000000..fb00843f2c --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java @@ -0,0 +1,347 @@ +/******************************************************************************* + * Copyright (c) 2002, 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 - Initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.resources.refresh.win32; + + +/** + * Hooks for native methods involved with win32 auto-refresh callbacks. + */ +public class Win32Natives { + /* general purpose */ + /** + * A general use constant expressing the value of an + * invalid handle. + */ + public static final long INVALID_HANDLE_VALUE; + /** + * An error constant which indicates that the previous function + * succeeded. + */ + public static final int ERROR_SUCCESS; + /** + * An error constant which indicates that a handle is or has become + * invalid. + */ + public static final int ERROR_INVALID_HANDLE; + /** + * The combination of all of the error constants. + */ + public static int FILE_NOTIFY_ALL; + /** + * A constant which indicates the maximum number of objects + * that can be passed into WaitForMultipleObjects. + */ + public static final int MAXIMUM_WAIT_OBJECTS; + /** + * A constant which indicates the maximum length of a pathname. + */ + public static final int MAX_PATH; + /** + * A constant which expresses the concept of the infinite. + */ + public static final int INFINITE; + + /* wait return values */ + /** + * A constant used returned WaitForMultipleObjects when the function times out. + */ + public static final int WAIT_TIMEOUT; + /** + * A constant used by WaitForMultipleObjects to indicate the object which was + * signaled. + */ + public static final int WAIT_OBJECT_0; + /** + * A constant returned by WaitForMultipleObjects which indicates + * that the wait failed. + */ + public static final int WAIT_FAILED; + + /* wait notification filter masks */ + /** + * Change filter for monitoring file rename, creation or deletion. + */ + public static final int FILE_NOTIFY_CHANGE_FILE_NAME; + /** + * Change filter for monitoring directory creation or deletion. + */ + public static final int FILE_NOTIFY_CHANGE_DIR_NAME; + /** + * Change filter for monitoring file/directory attribute changes. + */ + public static final int FILE_NOTIFY_CHANGE_ATTRIBUTES; + /** + * Change filter for monitoring file size changes. + */ + public static final int FILE_NOTIFY_CHANGE_SIZE; + /** + * Change filter for monitoring the file write timestamp + */ + public static final int FILE_NOTIFY_CHANGE_LAST_WRITE; + /** + * Change filter for monitoring the security descriptors + * of files. + */ + public static final int FILE_NOTIFY_CHANGE_SECURITY; + + /** + * Flag indicating whether or not the OS supports unicode calls. + */ + public static final boolean UNICODE; + /* + * Make requests to set the constants. + */ + static { + System.loadLibrary("win32refresh"); //$NON-NLS-1$ + UNICODE= IsUnicode(); + INVALID_HANDLE_VALUE= INVALID_HANDLE_VALUE(); + ERROR_SUCCESS= ERROR_SUCCESS(); + ERROR_INVALID_HANDLE= ERROR_INVALID_HANDLE(); + + MAXIMUM_WAIT_OBJECTS= MAXIMUM_WAIT_OBJECTS(); + MAX_PATH= MAX_PATH(); + INFINITE= INFINITE(); + + WAIT_TIMEOUT= WAIT_TIMEOUT(); + WAIT_OBJECT_0= WAIT_OBJECT_0(); + WAIT_FAILED= WAIT_FAILED(); + + FILE_NOTIFY_CHANGE_FILE_NAME= FILE_NOTIFY_CHANGE_FILE_NAME(); + FILE_NOTIFY_CHANGE_DIR_NAME= FILE_NOTIFY_CHANGE_DIR_NAME(); + FILE_NOTIFY_CHANGE_ATTRIBUTES= FILE_NOTIFY_CHANGE_ATTRIBUTES(); + FILE_NOTIFY_CHANGE_SIZE= FILE_NOTIFY_CHANGE_SIZE(); + FILE_NOTIFY_CHANGE_LAST_WRITE= FILE_NOTIFY_CHANGE_LAST_WRITE(); + FILE_NOTIFY_CHANGE_SECURITY= FILE_NOTIFY_CHANGE_SECURITY(); + FILE_NOTIFY_ALL= + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_ATTRIBUTES | + FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE | + FILE_NOTIFY_CHANGE_SECURITY; + } + + /** + * Creates a change notification object for the given path. The notification + * object allows the client to monitor changes to the directory and the + * subtree under the directory using FindNextChangeNotification or + * WaitForMultipleObjects. + *

          + * If the OS supports unicode the path must be no longer than 2^15 - 1 characters. + * Otherwise, the path cannot be longer than MAX_PATH. In either case, if the given + * path is too long ERROR_INVALID_HANDLE is returned. + * + * @param lpPathName The path of the file. + * @param bWatchSubtree If true, specifies that the entire + * tree under the given path should be monitored. If false + * specifies that just the named path should be monitored. + * @param dwNotifyFilter Any combination of FILE_NOTIFY_CHANGE_FILE_NAME, + * FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_ATTRIBUTES, + * FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_CHANGE_LAST_WRITE, or + * FILE_NOTIFY_CHANGE_SECURITY. + * @return long The handle to the find change notification object or + * ERROR_INVALID_HANDLE if the attempt fails. + */ + public static long FindFirstChangeNotification(String lpPathName, boolean bWatchSubtree, int dwNotifyFilter) { + if (UNICODE) + return FindFirstChangeNotificationW(lpPathName, bWatchSubtree, dwNotifyFilter); + return FindFirstChangeNotificationA(Convert.toPlatformBytes(lpPathName), bWatchSubtree, dwNotifyFilter); + } + /** + * Creates a change notification object for the given path. This notification object + * allows the client to monitor changes to the directory and the subtree + * under the directory using FindNextChangeNotification or + * WaitForMultipleObjects. + * + * @param lpPathName The path to the directory to be monitored. Cannot be null, + * or longer than 2^15 - 1 characters. + * @param bWatchSubtree If true, specifies that the entire + * tree under the given path should be monitored. If false + * specifies that just the named path should be monitored. + * @param dwNotifyFilter Any combination of FILE_NOTIFY_CHANGE_FILE_NAME, + * FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_ATTRIBUTES, + * FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_CHANGE_LAST_WRITE, or + * FILE_NOTIFY_CHANGE_SECURITY. + * @return long The handle to the find change notification object or + * ERROR_INVALID_HANDLE if the attempt fails. + */ + private static native long FindFirstChangeNotificationW(String lpPathName, boolean bWatchSubtree, int dwNotifyFilter); + + /** + * Creates a change notification object for the given path. This notification object + * allows the client to monitor changes to the directory and the subtree + * under the directory using FindNextChangeNotification or + * WaitForMultipleObjects. + * + * @param lpPathName The path to the directory to be monitored, cannot be null, + * or be longer + * than MAX_PATH. This path must be in platform bytes converted. + * @param bWatchSubtree If true, specifies that the entire + * tree under the given path should be monitored. If false + * specifies that just the named path should be monitored. + * @param dwNotifyFilter Any combination of FILE_NOTIFY_CHANGE_FILE_NAME, + * FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_ATTRIBUTES, + * FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_CHANGE_LAST_WRITE, or + * FILE_NOTIFY_CHANGE_SECURITY. + * @return long The handle to the find change notification object or + * ERROR_INVALID_HANDLE if the attempt fails. + */ + private static native long FindFirstChangeNotificationA(byte[] lpPathName, boolean bWatchSubtree, int dwNotifyFilter); + + + /** + * Stops and disposes of the change notification object that corresponds to the given + * handle. The handle cannot be used in future calls to FindNextChangeNotification or + * WaitForMultipleObjects + * + * @param hChangeHandle a handle which was created with FindFirstChangeNotification + * @return boolean true if the method succeeds, false + * otherwise. + */ + public static native boolean FindCloseChangeNotification(long hChangeHandle); + + /** + * Requests that the next change detected be signaled. This method should only be + * called after FindFirstChangeNotification or WaitForMultipleObjects. Once this + * method has been called on a given handle, further notification requests can be made + * through the WaitForMultipleObjects call. + * @param hChangeHandle a handle which was created with FindFirstChangeNotification + * @return boolean true if the method succeeds, false otherwise. + */ + public static native boolean FindNextChangeNotification(long hChangeHandle); + + /** + * Returns when one of the following occurs. + *

            + *
          • One of the objects is signaled, when bWaitAll is false
          • + *
          • All of the objects are signaled, when bWaitAll is true
          • + *
          • The timeout interval of dwMilliseconds elapses.
          • + *
          + * @param nCount The number of handles, cannot be greater than MAXIMUM_WAIT_OBJECTS. + * @param lpHandles The array of handles to objects to be waited upon cannot contain + * duplicate handles. + * @param bWaitAll If true requires all objects to be signaled before this + * method returns. If false, indicates that only one object need be + * signaled for this method to return. + * @param dwMilliseconds A timeout value in milliseconds. If zero, the function tests + * the objects and returns immediately. If INFINITE, the function will only return + * when the objects have been signaled. + * @return int WAIT_TIMEOUT when the function times out before recieving a signal. + * WAIT_OBJECT_0 + n when a signal for the handle at index n. WAIT_FAILED when this + * function fails. + */ + public static native int WaitForMultipleObjects(int nCount, long[] lpHandles, boolean bWaitAll, int dwMilliseconds); + + /** + * Answers true if the operating system supports + * long filenames. + * @return boolean true if the operating system supports + * long filenames, false otherwise. + */ + private static native boolean IsUnicode(); + + /** + * Answers the last error set in the current thread. + * @return int the last error + */ + public static native int GetLastError(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_LAST_WRITE. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_LAST_WRITE(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_DIR_NAME. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_DIR_NAME(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_ATTRIBUTES. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_ATTRIBUTES(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_SIZE. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_SIZE(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_FILE_NAME. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_FILE_NAME(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_SECURITY. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_SECURITY(); + + /** + * Returns the constant MAXIMUM_WAIT_OBJECTS. + * @return int + */ + private static native int MAXIMUM_WAIT_OBJECTS(); + + /** + * Returns the constant MAX_PATH. + * @return int + */ + private static native int MAX_PATH(); + + /** + * Returns the constant INFINITE. + * @return int + */ + private static native int INFINITE(); + + /** + * Returns the constant WAIT_OBJECT_0. + * @return int + */ + private static native int WAIT_OBJECT_0(); + + /** + * Returns the constant WAIT_FAILED. + * @return int + */ + private static native int WAIT_FAILED(); + + /** + * Returns the constant WAIT_TIMEOUT. + * @return int + */ + private static native int WAIT_TIMEOUT(); + + /** + * Returns the constant ERROR_INVALID_HANDLE. + * @return int + */ + private static native int ERROR_INVALID_HANDLE(); + + /** + * Returns the constant ERROR_SUCCESS. + * @return int + */ + private static native int ERROR_SUCCESS(); + + /** + * Returns the constant INVALID_HANDLE_VALUE. + * @return long + */ + private static native long INVALID_HANDLE_VALUE(); + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java new file mode 100644 index 0000000000..01092dcd5b --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.internal.resources.refresh.win32; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.refresh.*; + +/** + * The Win32RefreshProvider creates monitors that + * can monitor drives on Win32 platforms. + * + * @see org.eclipse.core.resources.refresh.RefreshProvider + */ +public class Win32RefreshProvider extends RefreshProvider { + private Win32Monitor monitor; + + /** + * Creates a standard Win32 monitor if the given resource is local. + * + * @see org.eclipse.core.resources.refresh.RefreshProvider#installMonitor(IResource,IRefreshResult) + */ + @Override + public IRefreshMonitor installMonitor(IResource resource, IRefreshResult result) { + if (resource.getLocation() == null || !resource.exists() || resource.getType() == IResource.FILE) + return null; + if (monitor == null) + monitor = new Win32Monitor(result); + if (monitor.monitor(resource)) + return monitor; + return null; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java new file mode 100644 index 0000000000..5357447992 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.utils; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * An object that iterates over the elements of an array + */ +public class ArrayIterator implements Iterator { + T[] elements; + int index; + int lastElement; + + /** + * Returns new array enumeration over the given object array + */ + public ArrayIterator(T[] elements) { + this(elements, 0, elements.length - 1); + } + + /** + * Returns new array enumeration over the given object array + */ + public ArrayIterator(T[] elements, int firstElement, int lastElement) { + super(); + this.elements = elements; + index = firstElement; + this.lastElement = lastElement; + } + + /** + * Returns true if this enumeration contains more elements. + */ + @Override + public boolean hasNext() { + return elements != null && index <= lastElement; + } + + /** + * Returns the next element of this enumeration. + * @exception NoSuchElementException if no more elements exist. + */ + @Override + public T next() throws NoSuchElementException { + if (!hasNext()) + throw new NoSuchElementException(); + return elements[index++]; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java new file mode 100644 index 0000000000..f3c03aef24 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 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.utils; + +/** + * Utility methods for manipulating bit masks + */ +public class BitMask { + /** + * Returns true if all of the bits indicated by the mask are set. + */ + public static boolean isSet(int flags, int mask) { + return (flags & mask) == mask; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java new file mode 100644 index 0000000000..be02eafc8a --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java @@ -0,0 +1,207 @@ +/******************************************************************************* + * 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.utils; + +import org.eclipse.core.runtime.Assert; + +/** + * A cache that keeps a collection of at most maximumCapacity+threshold entries. + * When the number of entries exceeds that limit, least recently used entries are removed + * so the current size is the same as the maximum capacity. + */ +public class Cache { + public class Entry implements KeyedHashSet.KeyedElement { + Object cached; + Object key; + Entry next; + Entry previous; + long timestamp; + + public Entry(Object key, Object cached, long timestamp) { + this.key = key; + this.cached = cached; + this.timestamp = timestamp; + } + + @Override + public boolean compare(KeyedHashSet.KeyedElement other) { + if (!(other instanceof Entry)) + return false; + Entry otherEntry = (Entry) other; + return key.equals(otherEntry.key); + } + + /* Removes this entry from the cache */ + public void discard() { + unchain(); + cached = null; + entries.remove(this); + } + + public Object getCached() { + return cached; + } + + @Override + public Object getKey() { + return key; + } + + @Override + public int getKeyHashCode() { + return key.hashCode(); + } + + public Entry getNext() { + return next; + } + + public Entry getPrevious() { + return previous; + } + + public long getTimestamp() { + return timestamp; + } + + public boolean isHead() { + return previous == null; + } + + public boolean isTail() { + return next == null; + } + + /* Inserts into the head of the list */ + void makeHead() { + Entry oldHead = head; + head = this; + next = oldHead; + previous = null; + if (oldHead == null) + tail = this; + else + oldHead.previous = this; + } + + public void setCached(Object cached) { + this.cached = cached; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public String toString() { + return key + " -> " + cached + " [" + timestamp + ']'; //$NON-NLS-1$ //$NON-NLS-2$ + } + + /* Removes from the linked list, but not from the entries collection */ + void unchain() { + // it may be in the tail + if (tail == this) + tail = previous; + else + next.previous = previous; + // it may be in the head + if (head == this) + head = next; + else + previous.next = next; + } + } + + KeyedHashSet entries; + Entry head; + private int maximumCapacity; + Entry tail; + private double threshold; + + public Cache(int maximumCapacity) { + this(Math.min(KeyedHashSet.MINIMUM_SIZE, maximumCapacity), maximumCapacity, 0.25); + } + + public Cache(int initialCapacity, int maximumCapacity, double threshold) { + Assert.isTrue(maximumCapacity >= initialCapacity, "maximum capacity < initial capacity"); //$NON-NLS-1$ + Assert.isTrue(threshold >= 0 && threshold <= 1, "threshold should be between 0 and 1"); //$NON-NLS-1$ + Assert.isTrue(initialCapacity > 0, "initial capacity must be greater than zero"); //$NON-NLS-1$ + entries = new KeyedHashSet(initialCapacity); + this.maximumCapacity = maximumCapacity; + this.threshold = threshold; + } + + public void addEntry(Object key, Object toCache) { + addEntry(key, toCache, 0); + } + + public Entry addEntry(Object key, Object toCache, long timestamp) { + Entry newHead = (Entry) entries.getByKey(key); + if (newHead == null) + entries.add(newHead = new Entry(key, toCache, timestamp)); + newHead.cached = toCache; + newHead.timestamp = timestamp; + newHead.makeHead(); + int extraEntries = entries.size() - maximumCapacity; + if (extraEntries > maximumCapacity * threshold) + // we have reached our limit - ensure we are under the maximum capacity + // by discarding older entries + packEntries(extraEntries); + return newHead; + } + + public Entry getEntry(Object key) { + return getEntry(key, true); + } + + public Entry getEntry(Object key, boolean update) { + Entry existing = (Entry) entries.getByKey(key); + if (existing == null) + return null; + if (!update) + return existing; + existing.unchain(); + existing.makeHead(); + return existing; + } + + public Entry getHead() { + return head; + } + + public Entry getTail() { + return tail; + } + + private void packEntries(int extraEntries) { + // should remove in an ad-hoc way to get better performance + Entry current = tail; + for (; current != null && extraEntries > 0; extraEntries--) { + current.discard(); + current = current.previous; + } + } + + public long size() { + return entries.size(); + } + + public void discardAll() { + entries.clear(); + head = tail = null; + } + + public void dispose() { + discardAll(); + entries = null; + head = tail = null; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java new file mode 100644 index 0000000000..231b5b35e7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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.utils; + +import java.io.UnsupportedEncodingException; + +public class Convert { + + /** + * Converts the string argument to a byte array. + */ + public static String fromUTF8(byte[] b) { + String result; + try { + result = new String(b, "UTF8"); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + result = new String(b); + } + return result; + } + + /** + * Converts the string argument to a byte array. + */ + public static byte[] toUTF8(String s) { + byte[] result; + try { + result = s.getBytes("UTF8"); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + result = s.getBytes(); + } + return result; + } + + /** + * Performs conversion of a long value to a byte array representation. + * + * @see #bytesToLong(byte[]) + */ + public static byte[] longToBytes(long value) { + + // A long value is 8 bytes in length. + byte[] bytes = new byte[8]; + + // Convert and copy value to byte array: + // -- Cast long to a byte to retrieve least significant byte; + // -- Left shift long value by 8 bits to isolate next byte to be converted; + // -- Repeat until all 8 bytes are converted (long = 64 bits). + // Note: In the byte array, the least significant byte of the long is held in + // the highest indexed array bucket. + + for (int i = 0; i < bytes.length; i++) { + bytes[(bytes.length - 1) - i] = (byte) value; + value >>>= 8; + } + + return bytes; + } + + /** + * Performs conversion of a byte array to a long representation. + * + * @see #longToBytes(long) + */ + public static long bytesToLong(byte[] value) { + + long longValue = 0L; + + // See method convertLongToBytes(long) for algorithm details. + for (int i = 0; i < value.length; i++) { + // Left shift has no effect thru first iteration of loop. + longValue <<= 8; + longValue ^= value[i] & 0xFF; + } + + return longValue; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java new file mode 100644 index 0000000000..8ca0ba4718 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java @@ -0,0 +1,436 @@ +/******************************************************************************* + * 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 + * Martin Oberhuber (Wind River) - [44107] Add symbolic links to ResourceAttributes API + * James Blackburn (Broadcom Corp.) - ongoing development + * Sergey Prigogin (Google) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import java.io.*; +import java.net.URI; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.resources.ResourceException; +import org.eclipse.core.internal.resources.Workspace; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.preferences.DefaultScope; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.osgi.service.environment.Constants; +import org.eclipse.osgi.util.NLS; +import org.osgi.service.prefs.BackingStoreException; +import org.osgi.service.prefs.Preferences; + +/** + * Static utility methods for manipulating Files and URIs. + */ +public class FileUtil { + static final boolean MACOSX = Constants.OS_MACOSX.equals(getOS()); + + /** + * Converts a ResourceAttributes object into an IFileInfo object. + * @param attributes The resource attributes + * @return The file info + */ + public static IFileInfo attributesToFileInfo(ResourceAttributes attributes) { + IFileInfo fileInfo = EFS.createFileInfo(); + fileInfo.setAttribute(EFS.ATTRIBUTE_READ_ONLY, attributes.isReadOnly()); + fileInfo.setAttribute(EFS.ATTRIBUTE_EXECUTABLE, attributes.isExecutable()); + fileInfo.setAttribute(EFS.ATTRIBUTE_ARCHIVE, attributes.isArchive()); + fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, attributes.isHidden()); + fileInfo.setAttribute(EFS.ATTRIBUTE_SYMLINK, attributes.isSymbolicLink()); + fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_READ, attributes.isSet(EFS.ATTRIBUTE_GROUP_READ)); + fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_WRITE, attributes.isSet(EFS.ATTRIBUTE_GROUP_WRITE)); + fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE, attributes.isSet(EFS.ATTRIBUTE_GROUP_EXECUTE)); + fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_READ, attributes.isSet(EFS.ATTRIBUTE_OTHER_READ)); + fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_WRITE, attributes.isSet(EFS.ATTRIBUTE_OTHER_WRITE)); + fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE, attributes.isSet(EFS.ATTRIBUTE_OTHER_EXECUTE)); + return fileInfo; + } + + /** + * Converts an IPath into its canonical form for the local file system. + */ + public static IPath canonicalPath(IPath path) { + if (path == null) + return null; + try { + final String pathString = path.toOSString(); + final String canonicalPath = new java.io.File(pathString).getCanonicalPath(); + //only create a new path if necessary + if (canonicalPath.equals(pathString)) + return path; + return new Path(canonicalPath); + } catch (IOException e) { + return path; + } + } + + /** + * For a path on a case-insensitive file system returns the path with the actual + * case as it exists in the file system. If only a prefix of the path exists on + * the file system, the case of remaining part of the returned path is the same + * as in the original path. For a case-sensitive file system returns the original + * path. + *

          + * This method is similar to java.nio.file.Path.toRealPath(LinkOption.NOFOLLOW_LINKS) + * in Java 1.7. + */ + public static IPath realPath(IPath path) { + if (path == null) + return null; + IFileSystem fileSystem = EFS.getLocalFileSystem(); + if (fileSystem.isCaseSensitive()) + return path; + IPath realPath = path.isAbsolute() ? Path.ROOT : Path.EMPTY; + String device = path.getDevice(); + if (device != null) { + realPath = realPath.setDevice(device.toUpperCase()); + } + IFileStore fileStore = null; + for (int i = 0; i < path.segmentCount(); i++) { + final String segment = path.segment(i); + if (i == 0 && path.isUNC()) { + realPath = realPath.append(segment.toUpperCase()); + realPath = realPath.makeUNC(true); + } else { + if (MACOSX) { + // IFileInfo.getName() may not return the real name of the file on Mac OS X. + // Obtain the real name of the file from a listing of its parent directory. + String[] names = realPath.toFile().list(new FilenameFilter() { + @Override + public boolean accept(File dir, String n) { + return n.equalsIgnoreCase(segment); + } + }); + String realName; + if (names == null || names.length == 0) { + // The remainder of the path doesn't exist on the file system - copy from + // the original path. + realPath = realPath.append(path.removeFirstSegments(realPath.segmentCount())); + break; + } else if (names.length == 1) { + realName = names[0]; + } else { + // More than one file matches the file name. Maybe the file system was + // misreported to be case insensitive. Preserve the original name. + realName = segment; + } + realPath = realPath.append(realName); + } else { + if (fileStore == null) + fileStore = fileSystem.getStore(realPath); + fileStore = fileStore.getChild(segment); + IFileInfo info = fileStore.fetchInfo(); + if (!info.exists()) { + // The remainder of the path doesn't exist on the file system - copy from + // the original path. + realPath = realPath.append(path.removeFirstSegments(realPath.segmentCount())); + break; + } + realPath = realPath.append(info.getName()); + } + } + } + if (path.hasTrailingSeparator()) { + realPath = realPath.addTrailingSeparator(); + } + // Return the original path if it's the same as the real one. + return realPath.equals(path) ? path : realPath; + } + + /** + * Returns the current OS. Equivalent to Platform.getOS(), but tolerant of the platform runtime + * not being present. + */ + private static String getOS() { + return System.getProperty("osgi.os", ""); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Converts a URI into its canonical form. + */ + public static URI canonicalURI(URI uri) { + if (uri == null) + return null; + if (EFS.SCHEME_FILE.equals(uri.getScheme())) { + //only create a new URI if it is different + final IPath inputPath = URIUtil.toPath(uri); + final IPath canonicalPath = canonicalPath(inputPath); + if (inputPath == canonicalPath) + return uri; + return URIUtil.toURI(canonicalPath); + } + return uri; + } + + /** + * Converts a URI by replacing the file system path in the URI with the path + * with the actual case as it exists in the file system. + * + * @see #realPath(IPath) + */ + public static URI realURI(URI uri) { + if (uri == null) + return null; + if (EFS.SCHEME_FILE.equals(uri.getScheme())) { + // Only create a new URI if it is different. + final IPath inputPath = URIUtil.toPath(uri); + final IPath realPath = realPath(inputPath); + if (inputPath == realPath) + return uri; + return URIUtil.toURI(realPath); + } + return uri; + } + + /** + * Returns true if the given file system locations overlap. If "bothDirections" is true, + * this means they are the same, or one is a proper prefix of the other. If "bothDirections" + * is false, this method only returns true if the locations are the same, or the first location + * is a prefix of the second. Returns false if the locations do not overlap + * Does the right thing with respect to case insensitive platforms. + */ + private static boolean computeOverlap(IPath location1, IPath location2, boolean bothDirections) { + IPath one = location1; + IPath two = location2; + // If we are on a case-insensitive file system then convert to all lower case. + if (!Workspace.caseSensitive) { + one = new Path(location1.toOSString().toLowerCase()); + two = new Path(location2.toOSString().toLowerCase()); + } + return one.isPrefixOf(two) || (bothDirections && two.isPrefixOf(one)); + } + + /** + * Returns true if the given file system locations overlap. If "bothDirections" is true, + * this means they are the same, or one is a proper prefix of the other. If "bothDirections" + * is false, this method only returns true if the locations are the same, or the first location + * is a prefix of the second. Returns false if the locations do not overlap + */ + private static boolean computeOverlap(URI location1, URI location2, boolean bothDirections) { + if (location1.equals(location2)) + return true; + String scheme1 = location1.getScheme(); + String scheme2 = location2.getScheme(); + if (scheme1 == null ? scheme2 != null : !scheme1.equals(scheme2)) + return false; + if (EFS.SCHEME_FILE.equals(scheme1) && EFS.SCHEME_FILE.equals(scheme2)) + return computeOverlap(URIUtil.toPath(location1), URIUtil.toPath(location2), bothDirections); + IFileSystem system = null; + try { + system = EFS.getFileSystem(scheme1); + } catch (CoreException e) { + //handled below + } + if (system == null) { + //we are stuck with string comparison + String string1 = location1.toString(); + String string2 = location2.toString(); + return string1.startsWith(string2) || (bothDirections && string2.startsWith(string1)); + } + IFileStore store1 = system.getStore(location1); + IFileStore store2 = system.getStore(location2); + return store1.equals(store2) || store1.isParentOf(store2) || (bothDirections && store2.isParentOf(store1)); + } + + /** + * Converts an IFileInfo object into a ResourceAttributes object. + * @param fileInfo The file info + * @return The resource attributes + */ + public static ResourceAttributes fileInfoToAttributes(IFileInfo fileInfo) { + ResourceAttributes attributes = new ResourceAttributes(); + attributes.setReadOnly(fileInfo.getAttribute(EFS.ATTRIBUTE_READ_ONLY)); + attributes.setArchive(fileInfo.getAttribute(EFS.ATTRIBUTE_ARCHIVE)); + attributes.setExecutable(fileInfo.getAttribute(EFS.ATTRIBUTE_EXECUTABLE)); + attributes.setHidden(fileInfo.getAttribute(EFS.ATTRIBUTE_HIDDEN)); + attributes.setSymbolicLink(fileInfo.getAttribute(EFS.ATTRIBUTE_SYMLINK)); + attributes.set(EFS.ATTRIBUTE_GROUP_READ, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_READ)); + attributes.set(EFS.ATTRIBUTE_GROUP_WRITE, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_WRITE)); + attributes.set(EFS.ATTRIBUTE_GROUP_EXECUTE, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE)); + attributes.set(EFS.ATTRIBUTE_OTHER_READ, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_READ)); + attributes.set(EFS.ATTRIBUTE_OTHER_WRITE, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_WRITE)); + attributes.set(EFS.ATTRIBUTE_OTHER_EXECUTE, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE)); + return attributes; + } + + private static String getLineSeparatorFromPreferences(Preferences node) { + try { + // be careful looking up for our node so not to create any nodes as side effect + if (node.nodeExists(Platform.PI_RUNTIME)) + return node.node(Platform.PI_RUNTIME).get(Platform.PREF_LINE_SEPARATOR, null); + } catch (BackingStoreException e) { + // ignore + } + return null; + } + + /** + * Returns line separator appropriate for the given file. The returned value + * will be the first available value from the list below: + *

            + *
          1. Line separator currently used in that file. + *
          2. Line separator defined in project preferences. + *
          3. Line separator defined in instance preferences. + *
          4. Line separator defined in default preferences. + *
          5. Operating system default line separator. + *
          + * @param file the file for which line separator should be returned + * @return line separator for the given file + */ + public static String getLineSeparator(IFile file) { + if (file.exists()) { + InputStream input = null; + try { + input = file.getContents(); + int c = input.read(); + while (c != -1 && c != '\r' && c != '\n') + c = input.read(); + if (c == '\n') + return "\n"; //$NON-NLS-1$ + if (c == '\r') { + if (input.read() == '\n') + return "\r\n"; //$NON-NLS-1$ + return "\r"; //$NON-NLS-1$ + } + } catch (CoreException e) { + // ignore + } catch (IOException e) { + // ignore + } finally { + safeClose(input); + } + } + Preferences rootNode = Platform.getPreferencesService().getRootNode(); + String value = null; + // if the file does not exist or has no content yet, try with project preferences + value = getLineSeparatorFromPreferences(rootNode.node(ProjectScope.SCOPE).node(file.getProject().getName())); + if (value != null) + return value; + // try with instance preferences + value = getLineSeparatorFromPreferences(rootNode.node(InstanceScope.SCOPE)); + if (value != null) + return value; + // try with default preferences + value = getLineSeparatorFromPreferences(rootNode.node(DefaultScope.SCOPE)); + if (value != null) + return value; + // if there is no preference set, fall back to OS default value + return System.getProperty("line.separator"); //$NON-NLS-1$ + } + + /** + * Returns true if the given file system locations overlap, and false otherwise. + * Overlap means the locations are the same, or one is a proper prefix of the other. + */ + public static boolean isOverlapping(URI location1, URI location2) { + return computeOverlap(location1, location2, true); + } + + /** + * Returns true if location1 is the same as, or a proper prefix of, location2. + * Returns false otherwise. + */ + public static boolean isPrefixOf(IPath location1, IPath location2) { + return computeOverlap(location1, location2, false); + } + + /** + * Returns true if location1 is the same as, or a proper prefix of, location2. + * Returns false otherwise. + */ + public static boolean isPrefixOf(URI location1, URI location2) { + return computeOverlap(location1, location2, false); + } + + /** + * Closes a stream and ignores any resulting exception. This is useful + * when doing stream cleanup in a finally block where secondary exceptions + * are not worth logging. + * + *

          + * WARNING: + * If the API contract requires notifying clients of I/O problems, then you must + * explicitly close() output streams outside of safeClose(). + * Some OutputStreams will defer an IOException from write() to close(). So + * while the writes may 'succeed', ignoring the IOExcpetion will result in silent + * data loss. + *

          + *

          + * This method should only be used as a fail-safe to ensure resources are not + * leaked. + *

          + * See also: https://bugs.eclipse.org/bugs/show_bug.cgi?id=332543 + */ + public static void safeClose(Closeable stream) { + try { + if (stream != null) + stream.close(); + } catch (IOException e) { + //ignore + } + } + + /** + * Converts a URI to an IPath. Returns null if the URI cannot be represented + * as an IPath. + *

          + * Note this method differs from URIUtil in its handling of relative URIs + * as being relative to path variables. + */ + public static IPath toPath(URI uri) { + if (uri == null) + return null; + final String scheme = uri.getScheme(); + // null scheme represents path variable + if (scheme == null || EFS.SCHEME_FILE.equals(scheme)) + return new Path(uri.getSchemeSpecificPart()); + return null; + } + + public static final void transferStreams(InputStream source, OutputStream destination, String path, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + byte[] buffer = new byte[8192]; + while (true) { + int bytesRead = -1; + try { + bytesRead = source.read(buffer); + } catch (IOException e) { + String msg = NLS.bind(Messages.localstore_failedReadDuringWrite, path); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, new Path(path), msg, e); + } + try { + if (bytesRead == -1) { + // Bug 332543 - ensure we don't ignore failures on close() + destination.close(); + break; + } + destination.write(buffer, 0, bytesRead); + } catch (IOException e) { + String msg = NLS.bind(Messages.localstore_couldNotWrite, path); + throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, new Path(path), msg, e); + } + monitor.worked(1); + } + } finally { + safeClose(source); + safeClose(destination); + } + } + + /** + * Not intended for instantiation. + */ + private FileUtil() { + super(); + } +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.java new file mode 100644 index 0000000000..762175c72c --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.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.utils; + +/** + * A string pool participant is used for sharing strings between several + * unrelated parties. Typically a single StringPool instance + * will be created, and a group of participants will be asked to store their + * strings in the pool. This allows participants to share equal strings + * without creating explicit dependencies between each other. + *

          + * Clients may implement this interface. + *

          + * + * @see StringPool + * @since 3.1 + */ +public interface IStringPoolParticipant { + /** + * Instructs this participant to share its strings in the provided + * pool. + */ + public void shareStrings(StringPool pool); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java new file mode 100644 index 0000000000..a597a8565a --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java @@ -0,0 +1,243 @@ +/******************************************************************************* + * 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.utils; + + +/** Adapted from a homonym class in org.eclipse.osgi. A hash table of + * KeyedElements. + */ + +public class KeyedHashSet { + public interface KeyedElement { + + public boolean compare(KeyedElement other); + + public Object getKey(); + + public int getKeyHashCode(); + } + + protected static final int MINIMUM_SIZE = 7; + private int capacity; + protected int elementCount = 0; + protected KeyedElement[] elements; + protected boolean replace; + + public KeyedHashSet(int capacity) { + this(capacity, true); + } + + public KeyedHashSet(int capacity, boolean replace) { + elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)]; + this.replace = replace; + this.capacity = capacity; + } + + /** + * Adds an element to this set. If an element with the same key already exists, + * replaces it depending on the replace flag. + * @return true if the element was added/stored, false otherwise + */ + public boolean add(KeyedElement element) { + int hash = hash(element); + + // 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 true; + } + if (elements[i].compare(element)) { + if (replace) + elements[i] = element; + return replace; + } + } + + // 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 true; + } + if (elements[i].compare(element)) { + if (replace) + elements[i] = element; + return replace; + } + } + + // if we didn't find a free slot, then try again with the expanded set + expand(); + return add(element); + } + + public void clear() { + elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)]; + elementCount = 0; + } + + /** + * The array isn't large enough so double its size and rehash + * all its current values. + */ + protected void expand() { + KeyedElement[] oldElements = elements; + elements = new KeyedElement[elements.length * 2]; + + int maxArrayIndex = elements.length - 1; + for (int i = 0; i < oldElements.length; i++) { + KeyedElement element = oldElements[i]; + if (element != null) { + int hash = hash(element); + while (elements[hash] != null) { + hash++; + if (hash > maxArrayIndex) + hash = 0; + } + elements[hash] = element; + } + } + } + + /** + * Returns the set element with the given id, or null + * if not found. + */ + public KeyedElement getByKey(Object key) { + if (elementCount == 0) + return null; + int hash = keyHash(key); + + // search the last half of the array + for (int i = hash; i < elements.length; i++) { + KeyedElement element = elements[i]; + if (element == null) + return null; + if (element.getKey().equals(key)) + return element; + } + + // search the beginning of the array + for (int i = 0; i < hash - 1; i++) { + KeyedElement element = elements[i]; + if (element == null) + return null; + if (element.getKey().equals(key)) + return element; + } + + // nothing found so return null + return null; + } + + private int hash(KeyedElement key) { + return Math.abs(key.getKeyHashCode()) % elements.length; + } + + private int keyHash(Object key) { + return Math.abs(key.hashCode()) % elements.length; + } + + /** + * 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; + KeyedElement element = elements[index]; + while (element != null) { + int hashIndex = hash(element); + 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 boolean remove(KeyedElement toRemove) { + if (elementCount == 0) + return false; + + int hash = hash(toRemove); + + for (int i = hash; i < elements.length; i++) { + KeyedElement element = elements[i]; + if (element == null) + return false; + if (element.compare(toRemove)) { + rehashTo(i); + elementCount--; + return true; + } + } + + for (int i = 0; i < hash - 1; i++) { + KeyedElement element = elements[i]; + if (element == null) + return false; + if (element.compare(toRemove)) { + rehashTo(i); + elementCount--; + return true; + } + } + return false; + } + + private boolean shouldGrow() { + return elementCount > elements.length * 0.75; + } + + public int size() { + return elementCount; + } + + @Override + public String toString() { + StringBuffer result = new StringBuffer(100); + result.append('{'); + boolean first = true; + for (int i = 0; i < elements.length; i++) { + if (elements[i] != null) { + if (first) + first = false; + else + result.append(", "); //$NON-NLS-1$ + result.append(elements[i]); + } + } + result.append('}'); + return result.toString(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java new file mode 100644 index 0000000000..5e849fd33a --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java @@ -0,0 +1,328 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + * Francis Lynch (Wind River) - [301563] Save and load tree snapshots + * Martin Oberhuber (Wind River) - [306575] Save snapshot location with project + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.core.internal.utils.messages"; //$NON-NLS-1$ + + // dtree + public static String dtree_immutable; + public static String dtree_malformedTree; + public static String dtree_missingChild; + public static String dtree_notFound; + public static String dtree_notImmutable; + public static String dtree_reverse; + public static String dtree_subclassImplement; + public static String dtree_switchError; + + // events + public static String events_builderError; + public static String events_building_0; + public static String events_building_1; + public static String events_errors; + public static String events_instantiate_1; + public static String events_invoking_1; + public static String events_invoking_2; + public static String events_skippingBuilder; + public static String events_unknown; + + public static String history_copyToNull; + public static String history_copyToSelf; + public static String history_errorContentDescription; + public static String history_notValid; + public static String history_problemsCleaning; + + public static String links_creating; + public static String links_errorLinkReconcile; + public static String links_invalidLocation; + public static String links_localDoesNotExist; + public static String links_locationOverlapsLink; + public static String links_locationOverlapsProject; + public static String links_natureVeto; + public static String links_noPath; + public static String links_overlappingResource; + public static String links_parentNotAccessible; + public static String links_notFileFolder; + public static String links_updatingDuplicate; + public static String links_vetoNature; + public static String links_workspaceVeto; + public static String links_wrongLocalType; + public static String links_resourceIsNotALink; + public static String links_setLocation; + + public static String group_invalidParent; + public static String filters_missingFilterType; + + // local store + public static String localstore_copying; + public static String localstore_copyProblem; + public static String localstore_couldnotDelete; + public static String localstore_couldNotMove; + public static String localstore_couldNotRead; + public static String localstore_couldNotWrite; + public static String localstore_couldNotWriteReadOnly; + public static String localstore_deleteProblem; + public static String localstore_deleting; + public static String localstore_failedReadDuringWrite; + public static String localstore_fileExists; + public static String localstore_fileNotFound; + public static String localstore_locationUndefined; + public static String localstore_refreshing; + public static String localstore_refreshingRoot; + public static String localstore_resourceExists; + public static String localstore_resourceDoesNotExist; + public static String localstore_resourceIsOutOfSync; + + // resource mappings and models + public static String mapping_invalidDef; + public static String mapping_wrongType; + public static String mapping_noIdentifier; + public static String mapping_validate; + public static String mapping_multiProblems; + + // internal.resources + public static String natures_duplicateNature; + public static String natures_hasCycle; + public static String natures_invalidDefinition; + public static String natures_invalidRemoval; + public static String natures_invalidSet; + public static String natures_missingIdentifier; + public static String natures_missingNature; + public static String natures_missingPrerequisite; + public static String natures_multipleSetMembers; + + public static String pathvar_beginLetter; + public static String pathvar_invalidChar; + public static String pathvar_invalidValue; + public static String pathvar_length; + public static String pathvar_undefined; + public static String pathvar_whitespace; + + public static String preferences_deleteException; + public static String preferences_loadException; + public static String preferences_operationCanceled; + public static String preferences_removeNodeException; + public static String preferences_clearNodeException; + public static String preferences_saveProblems; + public static String preferences_syncException; + + public static String projRead_badArguments; + public static String projRead_badFilterName; + public static String projRead_badFilterID; + public static String projRead_badFilterType; + public static String projRead_badFilterType2; + public static String projRead_badID; + public static String projRead_badLinkLocation; + public static String projRead_badLinkName; + public static String projRead_badLinkType; + public static String projRead_badLinkType2; + public static String projRead_badLocation; + public static String projRead_badSnapshotLocation; + public static String projRead_cannotReadSnapshot; + public static String projRead_emptyFilterName; + public static String projRead_emptyLinkName; + public static String projRead_emptyVariableName; + public static String projRead_failureReadingProjectDesc; + public static String projRead_notProjectDescription; + public static String projRead_whichKey; + public static String projRead_whichValue; + public static String projRead_missingProjectName; + + public static String properties_couldNotClose; + public static String properties_qualifierIsNull; + public static String properties_readProperties; + public static String properties_valueTooLong; + + // auto-refresh + public static String refresh_installError; + public static String refresh_jobName; + public static String refresh_pollJob; + public static String refresh_refreshErr; + public static String refresh_task; + + public static String resources_cannotModify; + public static String resources_changeInAdd; + public static String resources_charsetBroadcasting; + public static String resources_charsetUpdating; + public static String resources_closing_0; + public static String resources_closing_1; + public static String resources_copyDestNotSub; + public static String resources_copying; + public static String resources_copying_0; + public static String resources_copyNotMet; + public static String resources_copyProblem; + public static String resources_couldnotDelete; + public static String resources_create; + public static String resources_creating; + public static String resources_deleteMeta; + public static String resources_deleteProblem; + public static String resources_deleting; + public static String resources_deleting_0; + public static String resources_destNotNull; + public static String resources_errorContentDescription; + public static String resources_errorDeleting; + public static String resources_errorMarkersDelete; + public static String resources_errorMarkersMove; + public static String resources_wrongMarkerAttributeValueType; + public static String resources_errorMembers; + public static String resources_errorMoving; + public static String resources_errorMultiRefresh; + public static String resources_errorNature; + public static String resources_errorPropertiesMove; + public static String resources_errorReadProject; + public static String resources_errorRefresh; + public static String resources_errorValidator; + public static String resources_errorVisiting; + public static String resources_existsDifferentCase; + public static String resources_existsLocalDifferentCase; + public static String resources_exMasterTable; + public static String resources_exReadProjectLocation; + public static String resources_exSafeRead; + public static String resources_exSafeSave; + public static String resources_exSaveMaster; + public static String resources_exSaveProjectLocation; + public static String resources_fileExists; + public static String resources_fileToProj; + public static String resources_flushingContentDescriptionCache; + public static String resources_folderOverFile; + public static String resources_format; + public static String resources_initHook; + public static String resources_initTeamHook; + public static String resources_initValidator; + public static String resources_invalidCharInName; + public static String resources_invalidCharInPath; + public static String resources_invalidName; + public static String resources_invalidPath; + public static String resources_invalidProjDesc; + public static String resources_invalidResourceName; + public static String resources_invalidRoot; + public static String resources_markerNotFound; + public static String resources_missingProjectMeta; + public static String resources_missingProjectMetaRepaired; + public static String resources_moveDestNotSub; + public static String resources_moveMeta; + public static String resources_moveNotMet; + public static String resources_moveNotProject; + public static String resources_moveProblem; + public static String resources_moveRoot; + public static String resources_moving; + public static String resources_moving_0; + public static String resources_mustBeAbsolute; + public static String resources_mustBeLocal; + public static String resources_mustBeOpen; + public static String resources_mustExist; + public static String resources_mustNotExist; + public static String resources_nameEmpty; + public static String resources_nameNull; + public static String resources_natureClass; + public static String resources_natureDeconfig; + public static String resources_natureExtension; + public static String resources_natureFormat; + public static String resources_natureImplement; + public static String resources_notChild; + public static String resources_oneHook; + public static String resources_oneTeamHook; + public static String resources_oneValidator; + public static String resources_opening_1; + public static String resources_overlapWorkspace; + public static String resources_overlapProject; + public static String resources_pathNull; + public static String resources_projectDesc; + public static String resources_projectDescSync; + public static String resources_projectMustNotBeOpen; + public static String resources_projectPath; + public static String resources_pruningHistory; + public static String resources_reading; + public static String resources_readingEncoding; + public static String resources_readingSnap; + public static String resources_readMarkers; + public static String resources_readMeta; + public static String resources_readMetaWrongVersion; + public static String resources_readOnly; + public static String resources_readOnly2; + public static String resources_readProjectMeta; + public static String resources_readProjectTree; + public static String resources_readSync; + public static String resources_readWorkspaceMeta; + public static String resources_readWorkspaceMetaValue; + public static String resources_readWorkspaceSnap; + public static String resources_readWorkspaceTree; + public static String resources_refreshing; + public static String resources_refreshingRoot; + public static String resources_resetMarkers; + public static String resources_resetSync; + public static String resources_resourcePath; + public static String resources_saveOp; + public static String resources_saveProblem; + public static String resources_saveWarnings; + public static String resources_saving_0; + public static String resources_savingEncoding; + public static String resources_setDesc; + public static String resources_setLocal; + public static String resources_settingCharset; + public static String resources_settingContents; + public static String resources_settingDefaultCharsetContainer; + public static String resources_settingDerivedFlag; + public static String resources_shutdown; + public static String resources_shutdownProblems; + public static String resources_snapInit; + public static String resources_snapRead; + public static String resources_snapRequest; + public static String resources_snapshot; + public static String resources_startupProblems; + public static String resources_touch; + public static String resources_updating; + public static String resources_updatingEncoding; + public static String resources_workspaceClosed; + public static String resources_workspaceOpen; + public static String resources_writeMeta; + public static String resources_writeWorkspaceMeta; + public static String resources_errorResourceIsFiltered; + + public static String synchronizer_partnerNotRegistered; + + // URL + public static String url_badVariant; + public static String url_couldNotResolve_projectDoesNotExist; + public static String url_couldNotResolve_URLProtocolHandlerCanNotResolveURL; + public static String url_couldNotResolve_resourceLocationCanNotBeDetermined; + + // utils + public static String utils_clone; + public static String utils_stringJobName; + // watson + public static String watson_elementNotFound; + public static String watson_illegalSubtree; + public static String watson_immutable; + public static String watson_noModify; + public static String watson_nullArg; + public static String watson_unknown; + + // auto-refresh win32 native + public static String WM_beginTask; + public static String WM_errCloseHandle; + public static String WM_errCreateHandle; + public static String WM_errFindChange; + public static String WM_errors; + public static String WM_jobName; + public static String WM_nativeErr; + + static { + // initialize resource bundles + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java new file mode 100644 index 0000000000..dd5b0cd60f --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java @@ -0,0 +1,330 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import java.util.*; + +/** + * A specialized map implementation that is optimized for a + * small set of object keys. + * + * Implemented as a single array that alternates keys and values. + */ +@SuppressWarnings("unchecked") +public class ObjectMap implements Map, IStringPoolParticipant { + + // 8 attribute keys, 8 attribute values + protected static final int DEFAULT_SIZE = 16; + protected static final int GROW_SIZE = 10; + protected int count = 0; + protected Object[] elements = null; + + /** + * Creates a new object map of default size + */ + public ObjectMap() { + this(DEFAULT_SIZE); + } + + /** + * Creates a new object map. + * @param initialCapacity The initial number of elements that will fit in the map. + */ + public ObjectMap(int initialCapacity) { + if (initialCapacity > 0) + elements = new Object[Math.max(initialCapacity * 2, 0)]; + } + + /** + * Creates a new object map of the same size as the given map and + * populate it with the key/attribute pairs found in the map. + * @param map The entries in the given map will be added to the new map. + */ + public ObjectMap(Map map) { + this(map.size()); + putAll(map); + } + + /** + * @see Map#clear() + */ + @Override + public void clear() { + elements = null; + count = 0; + } + + /** + * @see java.lang.Object#clone() + */ + @Override + public Object clone() { + return new ObjectMap<>(this); + } + + /** + * @see Map#containsKey(java.lang.Object) + */ + @Override + public boolean containsKey(Object key) { + if (elements == null || count == 0) + return false; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return true; + return false; + } + + /** + * @see Map#containsValue(java.lang.Object) + */ + @Override + public boolean containsValue(Object value) { + if (elements == null || 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; + } + + /** + * @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 count == 0 ? Collections.EMPTY_SET : toHashMap().entrySet(); + } + + /** + * See Object#equals + */ + @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; + + //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; + } + + /** + * @see Map#get(java.lang.Object) + */ + @Override + public V get(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return (V) elements[i + 1]; + return null; + } + + /** + * The capacity of the map has been exceeded, grow the array by + * GROW_SIZE to accommodate more entries. + */ + protected void grow() { + Object[] expanded = new Object[elements.length + GROW_SIZE]; + System.arraycopy(elements, 0, expanded, 0, elements.length); + elements = expanded; + } + + /** + * See Object#hashCode + */ + @Override + public int hashCode() { + int hash = 0; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + hash += elements[i].hashCode(); + } + } + return hash; + } + + /** + * @see Map#isEmpty() + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * @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()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.add((K) elements[i]); + } + } + return result; + } + + /** + * @see Map#put(java.lang.Object, java.lang.Object) + */ + @Override + public V put(K key, V value) { + if (key == null) + throw new NullPointerException(); + if (value == null) + return remove(key); + + // handle the case where we don't have any attributes yet + if (elements == null) + elements = new Object[DEFAULT_SIZE]; + if (count == 0) { + elements[0] = key; + elements[1] = value; + count++; + return null; + } + + int emptyIndex = -1; + // replace existing value if it exists + for (int i = 0; i < elements.length; i += 2) { + if (elements[i] != null) { + if (elements[i].equals(key)) { + Object oldValue = elements[i + 1]; + elements[i + 1] = value; + return (V) oldValue; + } + } else if (emptyIndex == -1) { + // keep track of the first empty index + emptyIndex = i; + } + } + // this will put the emptyIndex greater than the size but + // that's ok because we will grow first. + if (emptyIndex == -1) + emptyIndex = count * 2; + + // otherwise add it to the list of elements. + // grow if necessary + if (elements.length <= (count * 2)) + grow(); + elements[emptyIndex] = key; + elements[emptyIndex + 1] = value; + count++; + return null; + } + + /** + * @see Map#putAll(java.util.Map) + */ + @Override + public void putAll(Map map) { + for (Map.Entry e : map.entrySet()) + put(e.getKey(), e.getValue()); + } + + /** + * @see Map#remove(java.lang.Object) + */ + @Override + public V remove(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null && elements[i].equals(key)) { + elements[i] = null; + Object result = elements[i + 1]; + elements[i + 1] = null; + count--; + return (V) result; + } + } + return null; + } + + /** + * @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; + 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); + } + } + + /** + * Creates a new hash map with the same contents as this map. + */ + private HashMap toHashMap() { + HashMap result = new HashMap<>(size()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.put((K) elements[i], (V) elements[i + 1]); + } + } + return result; + } + + /** + * @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()); + 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/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java new file mode 100644 index 0000000000..853f60d673 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java @@ -0,0 +1,181 @@ +/******************************************************************************* + * 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.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.service.debug.DebugOptions; +import org.eclipse.osgi.service.debug.DebugOptionsListener; +import org.osgi.framework.Bundle; + +public class Policy { + public static final DebugOptionsListener RESOURCES_DEBUG_OPTIONS_LISTENER = new DebugOptionsListener() { + @Override + public void optionsChanged(DebugOptions options) { + DEBUG = options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/debug", false); //$NON-NLS-1$ + + DEBUG_AUTO_REFRESH = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/refresh", false); //$NON-NLS-1$ + + DEBUG_BUILD_DELTA = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/delta", false); //$NON-NLS-1$ + DEBUG_BUILD_FAILURE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/failure", false); //$NON-NLS-1$ + DEBUG_BUILD_INTERRUPT = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/interrupt", false); //$NON-NLS-1$ + DEBUG_BUILD_INVOKING = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/invoking", false); //$NON-NLS-1$ + DEBUG_BUILD_NEEDED = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/needbuild", false); //$NON-NLS-1$ + DEBUG_BUILD_NEEDED_STACK = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/needbuildstack", false); //$NON-NLS-1$ + DEBUG_BUILD_STACK = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/stacktrace", false); //$NON-NLS-1$ + + DEBUG_CONTENT_TYPE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/contenttype", false); //$NON-NLS-1$ + DEBUG_CONTENT_TYPE_CACHE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/contenttype/cache", false); //$NON-NLS-1$ + DEBUG_HISTORY = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/history", false); //$NON-NLS-1$ + DEBUG_NATURES = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/natures", false); //$NON-NLS-1$ + DEBUG_NOTIFICATIONS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/notifications", false); //$NON-NLS-1$ + DEBUG_PREFERENCES = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/preferences", false); //$NON-NLS-1$ + + DEBUG_RESTORE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore", false); //$NON-NLS-1$ + DEBUG_RESTORE_MARKERS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/markers", false); //$NON-NLS-1$ + DEBUG_RESTORE_MASTERTABLE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/mastertable", false); //$NON-NLS-1$ + DEBUG_RESTORE_METAINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/metainfo", false); //$NON-NLS-1$ + DEBUG_RESTORE_SNAPSHOTS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/snapshots", false); //$NON-NLS-1$ + DEBUG_RESTORE_SYNCINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/syncinfo", false); //$NON-NLS-1$ + DEBUG_RESTORE_TREE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/tree", false); //$NON-NLS-1$ + + DEBUG_SAVE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save", false); //$NON-NLS-1$ + DEBUG_SAVE_MARKERS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/markers", false); //$NON-NLS-1$ + DEBUG_SAVE_MASTERTABLE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/mastertable", false); //$NON-NLS-1$ + DEBUG_SAVE_METAINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/metainfo", false); //$NON-NLS-1$ + DEBUG_SAVE_SYNCINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/syncinfo", false); //$NON-NLS-1$ + DEBUG_SAVE_TREE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/tree", false); //$NON-NLS-1$ + + DEBUG_STRINGS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/strings", false); //$NON-NLS-1$ + } + }; + + public static final boolean buildOnCancel = false; + //general debug flag for the plugin + public static boolean DEBUG = false; + + public static boolean DEBUG_AUTO_REFRESH = false; + + //debug constants + public static boolean DEBUG_BUILD_DELTA = false; + public static boolean DEBUG_BUILD_FAILURE = false; + public static boolean DEBUG_BUILD_INTERRUPT = false; + public static boolean DEBUG_BUILD_INVOKING = false; + public static boolean DEBUG_BUILD_NEEDED = false; + public static boolean DEBUG_BUILD_NEEDED_STACK = false; + public static boolean DEBUG_BUILD_STACK = false; + + public static boolean DEBUG_CONTENT_TYPE = false; + public static boolean DEBUG_CONTENT_TYPE_CACHE = false; + public static boolean DEBUG_HISTORY = false; + public static boolean DEBUG_NATURES = false; + public static boolean DEBUG_NOTIFICATIONS = false; + public static boolean DEBUG_PREFERENCES = false; + // Get timing information for restoring data + public static boolean DEBUG_RESTORE = false; + public static boolean DEBUG_RESTORE_MARKERS = false; + public static boolean DEBUG_RESTORE_MASTERTABLE = false; + + public static boolean DEBUG_RESTORE_METAINFO = false; + public static boolean DEBUG_RESTORE_SNAPSHOTS = false; + public static boolean DEBUG_RESTORE_SYNCINFO = false; + public static boolean DEBUG_RESTORE_TREE = false; + // Get timing information for save and snapshot data + public static boolean DEBUG_SAVE = false; + public static boolean DEBUG_SAVE_MARKERS = false; + public static boolean DEBUG_SAVE_MASTERTABLE = false; + + public static boolean DEBUG_SAVE_METAINFO = false; + public static boolean DEBUG_SAVE_SYNCINFO = false; + public static boolean DEBUG_SAVE_TREE = false; + public static boolean DEBUG_STRINGS = false; + public static int endOpWork = 1; + public static final long MAX_BUILD_DELAY = 1000; + + public static final long MIN_BUILD_DELAY = 100; + public static int opWork = 99; + public static final int totalWork = 100; + + public static void checkCanceled(IProgressMonitor monitor) { + if (monitor.isCanceled()) + throw new OperationCanceledException(); + } + + /** + * Print a debug message to the console. + * Prepend the message with the current date, the name of the current thread and the current job if present. + */ + public static void debug(String message) { + StringBuilder output = new StringBuilder(); + output.append(new Date(System.currentTimeMillis())); + output.append(" - ["); //$NON-NLS-1$ + output.append(Thread.currentThread().getName()); + output.append("] "); //$NON-NLS-1$ + Job currentJob = Job.getJobManager().currentJob(); + if (currentJob != null) { + output.append(currentJob.getClass().getName()); + output.append("("); //$NON-NLS-1$ + output.append(currentJob.getName()); + output.append("): "); //$NON-NLS-1$ + } + output.append(message); + System.out.println(output.toString()); + } + + /** + * Print a debug throwable to the console. + */ + public static void debug(Throwable t) { + StringWriter writer = new StringWriter(); + t.printStackTrace(new PrintWriter(writer)); + String str = writer.toString(); + if (str.endsWith("\n")) //$NON-NLS-1$ + str = str.substring(0, str.length() - 2); + debug(str); + } + + public static void log(int severity, String message, Throwable t) { + if (message == null) + message = ""; //$NON-NLS-1$ + log(new Status(severity, ResourcesPlugin.PI_RESOURCES, 1, message, t)); + } + + public static void log(IStatus status) { + final Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES); + if (bundle == null) + return; + Platform.getLog(bundle).log(status); + } + + /** + * Logs a throwable, assuming severity of error + * @param t + */ + public static void log(Throwable t) { + log(IStatus.ERROR, "Internal Error", t); //$NON-NLS-1$ + } + + public static IProgressMonitor monitorFor(IProgressMonitor monitor) { + return monitor == null ? new NullProgressMonitor() : monitor; + } + + public static IProgressMonitor subMonitorFor(IProgressMonitor monitor, int ticks) { + if (monitor == null) + return new NullProgressMonitor(); + if (monitor instanceof NullProgressMonitor) + return monitor; + return new SubProgressMonitor(monitor, ticks); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java new file mode 100644 index 0000000000..f8986cc1e4 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * 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.utils; + +import java.util.Collections; +import java.util.Iterator; + +/** + * A Queue of objects. + */ +@SuppressWarnings("unchecked") +public class Queue { + protected Object[] elements; + protected int head; + protected int tail; + protected boolean reuse; + + public Queue() { + this(20, false); + } + + /** + * The parameter reuse indicates what do you want to happen with + * the object reference when you remove it from the queue. If + * reuse is false the queue no longer holds a reference to the + * object when it is removed. If reuse is true you can use the + * method getNextAvailableObject to get an used object, set its + * new values and add it again to the queue. + */ + public Queue(int size, boolean reuse) { + elements = new Object[size]; + head = tail = 0; + this.reuse = reuse; + } + + public void add(T element) { + int newTail = increment(tail); + if (newTail == head) { + grow(); + newTail = tail + 1; + } + elements[tail] = element; + tail = newTail; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to decrement an index in the queue. + */ + public int decrement(int index) { + return (index == 0) ? (elements.length - 1) : index - 1; + } + + public T elementAt(int index) { + return (T)elements[index]; + } + + @SuppressWarnings("rawtypes") + public Iterator iterator() { + /**/ + if (isEmpty()) + return Collections.EMPTY_LIST.iterator(); + + /* if head < tail we can use the same array */ + if (head <= tail) + return new ArrayIterator(elements, head, tail - 1); + + /* otherwise we need to create a new array */ + Object[] newElements = new Object[size()]; + int end = (elements.length - head); + System.arraycopy(elements, head, newElements, 0, end); + System.arraycopy(elements, 0, newElements, end, tail); + return new ArrayIterator(newElements); + } + + /** + * Returns an object that has been removed from the queue, if any. + * The intention is to support reuse of objects that have already + * been processed and removed from the queue. Returns null if there + * are no available objects. + */ + public T getNextAvailableObject() { + int index = tail; + while (index != head) { + if (elements[index] != null) { + T result = (T)elements[index]; + elements[index] = null; + return result; + } + index = increment(index); + } + return null; + } + + protected void grow() { + int newSize = (int) (elements.length * 1.5); + Object[] newElements = new Object[newSize]; + if (tail >= head) + System.arraycopy(elements, head, newElements, head, size()); + else { + int newHead = newSize - (elements.length - head); + System.arraycopy(elements, 0, newElements, 0, tail + 1); + System.arraycopy(elements, head, newElements, newHead, (newSize - newHead)); + head = newHead; + } + elements = newElements; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to increment an index in the queue. + */ + public int increment(int index) { + return (index == (elements.length - 1)) ? 0 : index + 1; + } + + public int indexOf(T target) { + if (tail >= head) { + for (int i = head; i < tail; i++) + if (target.equals(elements[i])) + return i; + } else { + for (int i = head; i < elements.length; i++) + if (target.equals(elements[i])) + return i; + for (int i = 0; i < tail; i++) + if (target.equals(elements[i])) + return i; + } + return -1; + } + + public boolean isEmpty() { + return tail == head; + } + + public T peek() { + return (T)elements[head]; + } + + public T peekTail() { + return (T)elements[decrement(tail)]; + } + + public T remove() { + if (isEmpty()) + return null; + T result = peek(); + if (!reuse) + elements[head] = null; + head = increment(head); + return result; + } + + public T removeTail() { + T result = peekTail(); + tail = decrement(tail); + if (!reuse) + elements[tail] = null; + return result; + } + + public void reset() { + tail = head = 0; + } + + public int size() { + return tail > head ? (tail - head) : ((elements.length - head) + tail); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append('['); + int count = 0; + if (!isEmpty()) { + Iterator it = iterator(); + //only print a fixed number of elements to prevent debugger from choking + while (count < 100) { + sb.append(it.next()); + if (it.hasNext()) + sb.append(',').append(' '); + else + break; + } + } + if (count < size()) + sb.append('.').append('.').append('.'); + sb.append(']'); + return sb.toString(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java new file mode 100644 index 0000000000..3b272ee9c2 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import java.util.HashMap; + +/** + * A string pool is used for sharing strings in a way that eliminates duplicate + * equal strings. A string pool instance can be maintained over a long period + * of time, or used as a temporary structure during a string sharing pass over + * a data structure. + *

          + * This class is not intended to be subclassed by clients. + *

          + * + * @see IStringPoolParticipant + * @since 3.1 + */ +public final class StringPool { + private int savings; + private final HashMap map = new HashMap<>(); + + /** + * Creates a new string pool. + */ + public StringPool() { + super(); + } + + /** + * Adds a String to the pool. Returns a String + * that is equal to the argument but that is unique within this pool. + * @param string The string to add to the pool + * @return A string that is equal to the argument. + */ + public String add(String string) { + if (string == null) + return string; + Object result = map.get(string); + if (result != null) { + if (result != string) + savings += 44 + 2 * string.length(); + return (String) result; + } + map.put(string, string); + return string; + } + + /** + * Returns an estimate of the size in bytes that was saved by sharing strings in + * the pool. In particular, this returns the size of all strings that were added to the + * pool after an equal string had already been added. This value can be used + * to estimate the effectiveness of a string sharing operation, in order to + * determine if or when it should be performed again. + * + * In some cases this does not precisely represent the number of bytes that + * were saved. For example, say the pool already contains string S1. Now + * string S2, which is equal to S1 but not identical, is added to the pool five + * times. This method will return the size of string S2 multiplied by the + * number of times it was added, even though the actual savings in this case + * is only the size of a single copy of S2. + */ + public int getSavedStringCount() { + return savings; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java new file mode 100644 index 0000000000..a743542614 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * 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.utils; + +import java.util.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.*; +import org.osgi.framework.Bundle; + +/** + * Performs string sharing passes on all string pool participants registered + * with the platform. + */ +public class StringPoolJob extends Job { + private static final long INITIAL_DELAY = 10000;//ten seconds + private static final long RESCHEDULE_DELAY = 300000;//five minutes + private long lastDuration; + /** + * Stores all registered string pool participants, along with the scheduling + * rule required when running it. + */ + private Map participants = Collections.synchronizedMap(new HashMap(10)); + + private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$ + + public StringPoolJob() { + super(Messages.utils_stringJobName); + setSystem(true); + setPriority(DECORATE); + } + + /** + * Adds a string pool participant. The job periodically builds + * a string pool and asks all registered participants to share their strings in + * the pool. Once all participants have added their strings to the pool, the + * pool is discarded to avoid additional memory overhead. + * + * Adding a participant that is equal to a participant already registered will + * replace the scheduling rule associated with the participant, but will otherwise + * be ignored. + * + * @param participant The participant to add + * @param rule The scheduling rule that must be owned at the time the + * participant is called. This allows a participant to protect their data structures + * against access at unsafe times. + * + * @see #removeStringPoolParticipant(IStringPoolParticipant) + * @since 3.1 + */ + public void addStringPoolParticipant(IStringPoolParticipant participant, ISchedulingRule rule) { + participants.put(participant, rule); + if (getState() == Job.SLEEPING) + wakeUp(INITIAL_DELAY); + else + schedule(INITIAL_DELAY); + } + + /** + * Removes the indicated log listener from the set of registered string + * pool participants. If no such participant is registered, no action is taken. + * + * @param participant the participant to deregister + * @see #addStringPoolParticipant(IStringPoolParticipant, ISchedulingRule) + * @since 3.1 + */ + public void removeStringPoolParticipant(IStringPoolParticipant participant) { + participants.remove(participant); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + //if the system is shutting down, don't build + if (systemBundle.getState() == Bundle.STOPPING) + return Status.OK_STATUS; + + //copy current participants to handle concurrent additions and removals to map + Map.Entry[] entries = participants.entrySet().toArray(new Map.Entry[participants.size()]); + ISchedulingRule[] rules = new ISchedulingRule[entries.length]; + IStringPoolParticipant[] toRun = new IStringPoolParticipant[entries.length]; + for (int i = 0; i < toRun.length; i++) { + toRun[i] = entries[i].getKey(); + rules[i] = entries[i].getValue(); + } + final ISchedulingRule rule = MultiRule.combine(rules); + long start = -1; + int savings = 0; + final IJobManager jobManager = Job.getJobManager(); + try { + jobManager.beginRule(rule, monitor); + start = System.currentTimeMillis(); + savings = shareStrings(toRun, monitor); + } finally { + jobManager.endRule(rule); + } + if (start > 0) { + lastDuration = System.currentTimeMillis() - start; + if (Policy.DEBUG_STRINGS) + Policy.debug("String sharing saved " + savings + " bytes in: " + lastDuration); //$NON-NLS-1$ //$NON-NLS-2$ + } + //throttle frequency if it takes too long + long scheduleDelay = Math.max(RESCHEDULE_DELAY, lastDuration * 100); + if (Policy.DEBUG_STRINGS) + Policy.debug("Rescheduling string sharing job in: " + scheduleDelay); //$NON-NLS-1$ + schedule(scheduleDelay); + return Status.OK_STATUS; + } + + private int shareStrings(IStringPoolParticipant[] toRun, IProgressMonitor monitor) { + final StringPool pool = new StringPool(); + for (int i = 0; i < toRun.length; i++) { + if (monitor.isCanceled()) + break; + final IStringPoolParticipant current = toRun[i]; + SafeRunner.run(new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + //exceptions are already logged, so nothing to do + } + + @Override + public void run() { + current.shareStrings(pool); + } + }); + } + return pool.getSavedStringCount(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java new file mode 100644 index 0000000000..ba741a1249 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java @@ -0,0 +1,359 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Mickael Istria (Red Hat Inc.) - Bug 488938, 488937 + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import java.io.*; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.util.GregorianCalendar; +import java.util.Random; +import org.eclipse.core.runtime.Assert; + +public class UniversalUniqueIdentifier implements java.io.Serializable { + + /** + * All serializable objects should have a stable serialVersionUID + */ + private static final long serialVersionUID = 1L; + + /* INSTANCE FIELDS =============================================== */ + + private byte[] fBits = new byte[BYTES_SIZE]; + + /* NON-FINAL PRIVATE STATIC FIELDS =============================== */ + + private static BigInteger fgPreviousClockValue; + private static int fgClockAdjustment = 0; + private static int fgClockSequence = -1; + private static byte[] nodeAddress; + + static { + nodeAddress = computeNodeAddress(); + } + + /* PRIVATE STATIC FINAL FIELDS =================================== */ + + private static Random fgRandomNumberGenerator = new Random(); + + /* PUBLIC STATIC FINAL FIELDS ==================================== */ + + public static final int BYTES_SIZE = 16; + public static final byte[] UNDEFINED_UUID_BYTES = new byte[16]; + public static final int MAX_CLOCK_SEQUENCE = 0x4000; + public static final int MAX_CLOCK_ADJUSTMENT = 0x7FFF; + public static final int TIME_FIELD_START = 0; + public static final int TIME_FIELD_STOP = 6; + public static final int TIME_HIGH_AND_VERSION = 7; + public static final int CLOCK_SEQUENCE_HIGH_AND_RESERVED = 8; + public static final int CLOCK_SEQUENCE_LOW = 9; + public static final int NODE_ADDRESS_START = 10; + public static final int NODE_ADDRESS_BYTE_SIZE = 6; + + public static final int BYTE_MASK = 0xFF; + + public static final int HIGH_NIBBLE_MASK = 0xF0; + + public static final int LOW_NIBBLE_MASK = 0x0F; + + public static final int SHIFT_NIBBLE = 4; + + public static final int ShiftByte = 8; + + /** + UniversalUniqueIdentifier default constructor returns a + new instance that has been initialized to a unique value. + */ + public UniversalUniqueIdentifier() { + this.setVersion(1); + this.setVariant(1); + this.setTimeValues(); + this.setNode(getNodeAddress()); + } + + /** + Constructor that accepts the bytes to use for the instance.   The format + of the byte array is compatible with the toBytes() method. + +

          The constructor returns the undefined uuid if the byte array is invalid. + + @see #toBytes() + @see #BYTES_SIZE + */ + public UniversalUniqueIdentifier(byte[] byteValue) { + fBits = new byte[BYTES_SIZE]; + if (byteValue.length >= BYTES_SIZE) + System.arraycopy(byteValue, 0, fBits, 0, BYTES_SIZE); + } + + 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); + } + + private static BigInteger clockValueNow() { + GregorianCalendar now = new GregorianCalendar(); + BigInteger nowMillis = BigInteger.valueOf(now.getTime().getTime()); + BigInteger baseMillis = BigInteger.valueOf(now.getGregorianChange().getTime()); + + return (nowMillis.subtract(baseMillis).multiply(BigInteger.valueOf(10000L))); + } + + /** + Simply increases the visibility of Object's clone. + Otherwise, no new behaviour. + */ + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + Assert.isTrue(false, Messages.utils_clone); + return null; + } + } + + public static int compareTime(byte[] fBits1, byte[] fBits2) { + for (int i = TIME_FIELD_STOP; i >= 0; i--) + if (fBits1[i] != fBits2[i]) + return (0xFF & fBits1[i]) - (0xFF & fBits2[i]); + return 0; + } + + /** + * Answers the node address attempting to mask the IP + * address of this machine. + * + * @return byte[] the node address + */ + private static byte[] computeNodeAddress() { + + byte[] address = new byte[NODE_ADDRESS_BYTE_SIZE]; + + // Seed the secure randomizer with some oft-varying inputs + int thread = Thread.currentThread().hashCode(); + long time = System.currentTimeMillis(); + int objectId = System.identityHashCode(""); //$NON-NLS-1$ + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(byteOut); + byte[] ipAddress = getIPAddress(); + + try { + if (ipAddress != null) + out.write(ipAddress); + out.write(thread); + out.writeLong(time); + out.write(objectId); + out.close(); + } catch (IOException exc) { + //ignore the failure, we're just trying to come up with a random seed + } + byte[] rand = byteOut.toByteArray(); + + SecureRandom randomizer = new SecureRandom(rand); + randomizer.nextBytes(address); + + // set the MSB of the first octet to 1 to distinguish from IEEE node addresses + address[0] = (byte) (address[0] | (byte) 0x80); + + return address; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof UniversalUniqueIdentifier)) + return false; + + byte[] other = ((UniversalUniqueIdentifier) obj).fBits; + if (fBits == other) + return true; + if (fBits.length != other.length) + return false; + for (int i = 0; i < fBits.length; i++) { + if (fBits[i] != other[i]) + return false; + } + return true; + } + + /** + Answers the IP address of the local machine using the + Java API class InetAddress. + + @return byte[] the network address in network order + @see java.net.InetAddress#getLocalHost() + @see java.net.InetAddress#getAddress() + */ + protected static byte[] getIPAddress() { + try { + return InetAddress.getLocalHost().getAddress(); + } catch (UnknownHostException e) { + //valid for this to be thrown be a machine with no IP connection + //It is VERY important NOT to throw this exception + return null; + } catch (ArrayIndexOutOfBoundsException e) { + // there appears to be a bug in the VM if there is an alias + // see bug 354820. As above it is important not to throw this + return null; + } + } + + private static byte[] getNodeAddress() { + return nodeAddress; + } + + @Override + public int hashCode() { + return fBits[0] + fBits[3] + fBits[7] + fBits[11] + fBits[15]; + } + + private static int nextClockSequence() { + + if (fgClockSequence == -1) + fgClockSequence = (int) (fgRandomNumberGenerator.nextDouble() * MAX_CLOCK_SEQUENCE); + + fgClockSequence = (fgClockSequence + 1) % MAX_CLOCK_SEQUENCE; + + return fgClockSequence; + } + + private static BigInteger nextTimestamp() { + + BigInteger timestamp = clockValueNow(); + int timestampComparison; + + timestampComparison = timestamp.compareTo(fgPreviousClockValue); + + if (timestampComparison == 0) { + if (fgClockAdjustment == MAX_CLOCK_ADJUSTMENT) { + while (timestamp.compareTo(fgPreviousClockValue) == 0) + timestamp = clockValueNow(); + timestamp = nextTimestamp(); + } else + fgClockAdjustment++; + } else { + fgClockAdjustment = 0; + + if (timestampComparison < 0) + nextClockSequence(); + } + + return timestamp; + } + + private void setClockSequence(int clockSeq) { + int clockSeqHigh = (clockSeq >>> ShiftByte) & LOW_NIBBLE_MASK; + int reserved = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & HIGH_NIBBLE_MASK; + + fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) (reserved | clockSeqHigh); + fBits[CLOCK_SEQUENCE_LOW] = (byte) (clockSeq & BYTE_MASK); + } + + protected void setNode(byte[] bytes) { + + for (int index = 0; index < NODE_ADDRESS_BYTE_SIZE; index++) + fBits[index + NODE_ADDRESS_START] = bytes[index]; + } + + private void setTimestamp(BigInteger timestamp) { + BigInteger value = timestamp; + BigInteger bigByte = BigInteger.valueOf(256L); + BigInteger[] results; + int version; + int timeHigh; + + for (int index = TIME_FIELD_START; index < TIME_FIELD_STOP; index++) { + results = value.divideAndRemainder(bigByte); + value = results[0]; + fBits[index] = (byte) results[1].intValue(); + } + version = fBits[TIME_HIGH_AND_VERSION] & HIGH_NIBBLE_MASK; + timeHigh = value.intValue() & LOW_NIBBLE_MASK; + fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | version); + } + + protected synchronized void setTimeValues() { + this.setTimestamp(timestamp()); + this.setClockSequence(fgClockSequence); + } + + protected int setVariant(int variantIdentifier) { + int clockSeqHigh = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & LOW_NIBBLE_MASK; + int variant = variantIdentifier & LOW_NIBBLE_MASK; + + fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) ((variant << SHIFT_NIBBLE) | clockSeqHigh); + return (variant); + } + + protected void setVersion(int versionIdentifier) { + int timeHigh = fBits[TIME_HIGH_AND_VERSION] & LOW_NIBBLE_MASK; + int version = versionIdentifier & LOW_NIBBLE_MASK; + + fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | (version << SHIFT_NIBBLE)); + } + + private static BigInteger timestamp() { + BigInteger timestamp; + + if (fgPreviousClockValue == null) { + fgClockAdjustment = 0; + nextClockSequence(); + timestamp = clockValueNow(); + } else + timestamp = nextTimestamp(); + + fgPreviousClockValue = timestamp; + return fgClockAdjustment == 0 ? timestamp : timestamp.add(BigInteger.valueOf(fgClockAdjustment)); + } + + /** + This representation is compatible with the (byte[]) constructor. + + @see #UniversalUniqueIdentifier(byte[]) + */ + public byte[] toBytes() { + byte[] result = new byte[fBits.length]; + + System.arraycopy(fBits, 0, result, 0, fBits.length); + return result; + } + + @Override + public String toString() { + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < fBits.length; i++) + appendByteString(buffer, fBits[i]); + return buffer.toString(); + } + + public String toStringAsBytes() { + StringBuilder result = new StringBuilder("{"); //$NON-NLS-1$ + + for (int i = 0; i < fBits.length; i++) { + result.append(fBits[i]); + if (i < fBits.length + 1) + result.append(','); + } + result.append('}'); + return result.toString(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java new file mode 100644 index 0000000000..1d8e475408 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.utils; + +public class WrappedRuntimeException extends RuntimeException { + + /** + * All serializable objects should have a stable serialVersionUID + */ + private static final long serialVersionUID = 1L; + + private Throwable target; + + public WrappedRuntimeException(Throwable target) { + super(); + this.target = target; + } + + public Throwable getTargetException() { + return this.target; + } + + @Override + public String getMessage() { + return target.getMessage(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties new file mode 100644 index 0000000000..2e3ae9b1c5 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties @@ -0,0 +1,322 @@ +############################################################################### +# 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) - [252996] add resource filtering +# Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support +# Francis Lynch (Wind River) - [301563] Save and load tree snapshots +# Martin Oberhuber (Wind River) - [306575] Save snapshot location with project +############################################################################### +### Resources plugin messages. + +### dtree +dtree_immutable = Illegal attempt to modify an immutable tree. +dtree_malformedTree = Malformed tree. +dtree_missingChild = Missing child node: {0}. +dtree_notFound = Tree element ''{0}'' not found. +dtree_notImmutable = Tree must be immutable. +dtree_reverse = Tried to reverse a non-comparison tree. +dtree_subclassImplement = Subclass should have implemented this. +dtree_switchError = Switch error in DeltaTreeReader.readNode(). + +### events +events_builderError = Errors running builder ''{0}'' on project ''{1}''. +events_building_0 = Building workspace +events_building_1 = Building ''{0}'' +events_errors = Errors occurred during the build. +events_instantiate_1 = Error instantiating builder ''{0}''. +events_invoking_1 = Invoking builder on ''{0}''. +events_invoking_2 = Invoking ''{0}'' on ''{1}''. +events_skippingBuilder = Skipping builder ''{0}'' for project ''{1}''. Either the builder is missing from the install, or it belongs to a project nature that is missing or disabled. +events_unknown = {0} encountered while running {1}. + +history_copyToNull = Unable to copy local history to or from a null location. +history_copyToSelf = Unable to copy local history to and from the same location. +history_errorContentDescription = Error retrieving content description for local history for: ''{0}''. +history_notValid = State is not valid or might have expired. +history_problemsCleaning = Problems cleaning up history store. + +links_creating = Creating link. +links_errorLinkReconcile = Error processing changed links in project description file. +links_invalidLocation = ''{0}'' is not a valid location for linked resources. +links_localDoesNotExist = Cannot create linked resource. Local location ''{0}'' does not exist. +links_locationOverlapsLink = ''{0}'' is not a valid location because the project contains a linked resource at that location. +links_locationOverlapsProject = Cannot create a link to ''{0}'' because it overlaps the location of the project that contains the linked resource. +links_natureVeto = Linking is not allowed because project nature ''{0}'' does not allow it. +links_noPath = A Link location must be specified. +links_overlappingResource = Location ''{0}'' may overlap another resource. This can cause unexpected side-effects. +links_updatingDuplicate = Updating duplicate resource: ''{0}''. +links_parentNotAccessible = Cannot create linked resource ''{0}''. The parent resource is not accessible. +links_notFileFolder = Cannot create linked resource ''{0}''. Only files and folders can be linked. +links_vetoNature = Cannot add nature because project ''{0}'' contains linked resources, and nature ''{1}'' does not allow it. +links_workspaceVeto = Linked resources are not supported by this application. +links_wrongLocalType = Cannot create linked resource ''{0}''. Files cannot be linked to folders. +links_resourceIsNotALink=Resource ''{0}'' must be a linked resource to change its linked location. +links_setLocation=Changing link location. + +group_invalidParent = Only virtual folders and links can be created under a virtual folder. + +filters_missingFilterType= Missing resource filter type: ''{0}''. + +### local store +localstore_copying = Copying ''{0}''. +localstore_copyProblem = Problems encountered while copying resources. +localstore_couldnotDelete = Could not delete ''{0}''. +localstore_couldNotMove = Could not move ''{0}''. +localstore_couldNotRead = Could not read file ''{0}''. +localstore_couldNotWrite = Could not write file ''{0}''. +localstore_couldNotWriteReadOnly = Could not write to read-only file: ''{0}''. +localstore_deleteProblem = Problems encountered while deleting resources. +localstore_deleting = Deleting ''{0}''. +localstore_failedReadDuringWrite = Could not read from source when writing file ''{0}'' +localstore_fileExists = A resource already exists on disk ''{0}''. +localstore_fileNotFound = File not found: {0}. +localstore_locationUndefined = The location for ''{0}'' could not be determined because it is based on an undefined path variable. +localstore_refreshing = Refreshing ''{0}''. +localstore_refreshingRoot = Refreshing workspace. +localstore_resourceExists = Resource already exists on disk: ''{0}''. +localstore_resourceDoesNotExist = Resource does not exist on disk: ''{0}''. +localstore_resourceIsOutOfSync = Resource is out of sync with the file system: ''{0}''. + +### Resource mappings and models +mapping_invalidDef = Model provider extension found with invalid definition: {0}. +mapping_wrongType = Model provider ''{0}'' does not extend ModelProvider. +mapping_noIdentifier = Found model provider extension with no identifier; ignoring extension. +mapping_validate = Validating resource changes +mapping_multiProblems = Multiple potential side effects have been identified. + +### internal.resources +natures_duplicateNature = Duplicate nature: {0}. +natures_hasCycle = Nature is invalid because its prerequisites form a cycle: {0} +natures_missingIdentifier = Found nature extension with no identifier; ignoring extension. +natures_missingNature = Nature does not exist: {0}. +natures_missingPrerequisite = Nature {0} is missing prerequisite nature: {1}. +natures_multipleSetMembers = Multiple natures found for nature set: {0}. +natures_invalidDefinition = Nature extension found with invalid definition: {0}. +natures_invalidRemoval = Cannot remove nature {0} because it is a prerequisite of nature {1}. +natures_invalidSet = The set of natures is not valid. + +pathvar_length = Path variable name must have a length > 0. +pathvar_beginLetter = Path variable name must begin with a letter or underscore. +pathvar_invalidChar = Path variable name cannot contain character: {0}. +pathvar_invalidValue = Path variable value must be valid and absolute. +pathvar_undefined = ''{0}'' is not a valid location. The location is relative to undefined workspace path variable ''{1}''. +pathvar_whitespace= Path variable name cannot contain whitespace + +### preferences +preferences_deleteException=Exception deleting file: {0}. +preferences_loadException=Exception loading preferences from: {0}. +preferences_operationCanceled=Operation canceled. +preferences_removeNodeException=Exception while removing preference node: {0}. +preferences_clearNodeException=Exception while clearing preference node: {0}. +preferences_saveProblems=Exception occurred while saving project preferences: {0}. +preferences_syncException=Exception occurred while synchronizing node: {0}. + +projRead_badLinkName = Names ''{0}'' and ''{1}'' detected for a single link. Using ''{0}''. +projRead_badLinkType2 = Types ''{0}'' and ''{1}'' detected for a single link. Using ''{0}''. +projRead_badLocation = Locations ''{0}'' and ''{1}'' detected for a single link. Using ''{0}''. +projRead_emptyLinkName = Empty name detected for linked resource with type ''{0}'' and location ''{1}''. +projRead_badLinkType = Illegal link type \"-1\" detected for linked resource with name ''{0}'' and location ''{1}''. +projRead_badLinkLocation = Empty location detected for linked resource with name ''{0}'' and type ''{1}''. +projRead_whichKey = Two values detected for an argument name in a build command: ''{0}'' and ''{1}''. Using ''{0}''. +projRead_whichValue = Two values detected for an argument value in a build command: ''{0}'' and ''{1}''. Using ''{0}''. +projRead_notProjectDescription = Encountered element name ''{0}'' instead of \"project\" when trying to read a project description file. +projRead_badSnapshotLocation = Invalid resource snapshot location URI ''{0}'' is not absolute. +projRead_cannotReadSnapshot = Failed to read resource snapshot for project ''{0}'': {1} +projRead_failureReadingProjectDesc = Failure occurred reading .project file. +projRead_missingProjectName = Missing project name. + +projRead_emptyVariableName = Empty variable name detected in project "{0}\". + +projRead_badFilterName = Names ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. +projRead_emptyFilterName = Empty name detected for filtered resource with type ''{0}'' and id ''{1}''. +projRead_badFilterID = Empty filter id detected for filtered resource with name ''{0}'' and type ''{1}''. +projRead_badFilterType = Illegal filter type \"-1\" detected for filtered resource with name ''{0}'' and id ''{1}''. +projRead_badFilterType2 = Types ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. +projRead_badID = IDs ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. +projRead_badArguments= Arguments ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. + +properties_qualifierIsNull = Qualifier part of property key cannot be null. +properties_readProperties = Failure while reading persistent properties for resource ''{0}'', file was corrupt. Some properties may have been lost. +properties_valueTooLong = Could not set property: {0} {1}. Value is too long. +properties_couldNotClose = Could not close property store for: {0}. + +### auto-refresh +refresh_jobName = Refreshing workspace +refresh_task = Resources to refresh: {0} +refresh_pollJob = Searching for local changes +refresh_refreshErr = Problems occurred while refreshing local changes +refresh_installError = An error occurred while installing an auto-refresh monitor + +resources_cannotModify = The resource tree is locked for modifications. +resources_changeInAdd = Trying to change a marker in an add method. +resources_charsetBroadcasting = Reporting encoding changes. +resources_charsetUpdating = Updating encoding settings. +resources_closing_0 = Closing workspace. +resources_closing_1 = Closing ''{0}''. +resources_copyDestNotSub = Cannot copy ''{0}''. Destination should not be under source''s hierarchy. +resources_copying = Copying ''{0}''. +resources_copying_0 = Copying. +resources_copyNotMet = Copy requirements not met. +resources_copyProblem = Problems encountered while copying resources. +resources_couldnotDelete = Could not delete ''{0}''. +resources_create = Create. +resources_creating = Creating resource ''{0}''. +resources_deleteMeta = Could not delete metadata for ''{0}''. +resources_deleteProblem = Problems encountered while deleting resources. +resources_deleting = Deleting ''{0}''. +resources_deleting_0 = Deleting. +resources_destNotNull = Destination path should not be null. +resources_errorContentDescription = Error retrieving content description for resource ''{0}''. +resources_errorDeleting = Error deleting resource ''{0}'' from the workspace tree. +resources_errorMarkersDelete = Error deleting markers for resource ''{0}''. +resources_errorMarkersMove = Error moving markers from resource ''{0}'' to ''{1}''. +resources_wrongMarkerAttributeValueType = "The attribute value type is {0} and expected is one of java.lang.String, Boolean, Integer" +resources_errorMembers = Error retrieving members of container ''{0}''. +resources_errorMoving = Error moving resource ''{0}'' to ''{1}'' in the workspace tree. +resources_errorNature = Error configuring nature ''{0}''. +resources_errorPropertiesMove = Error moving properties for resource ''{0}'' to ''{1}''. +resources_errorRefresh = Errors occurred during refresh of resource ''{0}''. +resources_errorReadProject = Failed to read project description file from location ''{0}''. +resources_errorMultiRefresh = Errors occurred while refreshing resources with the local file system. +resources_errorValidator = Exception running validator code. +resources_errorVisiting = An error occurred while traversing resources. +resources_existsDifferentCase = A resource exists with a different case: ''{0}''. +resources_existsLocalDifferentCase = A resource exists on disk with a different case: ''{0}''. +resources_exMasterTable = Could not read master table. +resources_exReadProjectLocation = Could not read the project location for ''{0}''. +resources_exSafeRead = Could not read safe table. +resources_exSafeSave = Could not save safe table. +resources_exSaveMaster = Could not save master table to file ''{0}''. +resources_exSaveProjectLocation = Could not save the project location for ''{0}''. +resources_fileExists = A resource already exists on disk ''{0}''. +resources_fileToProj = Cannot copy a file to a project. +resources_flushingContentDescriptionCache = Flushing content description cache. +resources_folderOverFile = Cannot overwrite folder with file ''{0}''. +resources_format = Incompatible file format. Workspace was saved with an incompatible version: {0}. +resources_initValidator = Unable to instantiate validator. +resources_initHook = Unable to instantiate move/delete hook. +resources_initTeamHook = Unable to instantiate team hook. +resources_invalidCharInName = {0} is an invalid character in resource name ''{1}''. +resources_invalidCharInPath = {0} is an invalid character in path ''{1}''. +resources_invalidName = ''{0}'' is an invalid name on this platform. +resources_invalidPath = ''{0}'' is an invalid resource path. +resources_invalidProjDesc = Invalid project description. +resources_invalidResourceName = ''{0}'' is an invalid resource name. +resources_invalidRoot = Root (/) is an invalid resource path. +resources_markerNotFound = Marker id {0} not found. +resources_missingProjectMeta = The project description file (.project) for ''{0}'' is missing. This file contains important information about the project. The project will not function properly until this file is restored. +resources_missingProjectMetaRepaired = The project description file (.project) for ''{0}'' was missing. This file contains important information about the project. A new project description file has been created, but some information about the project may have been lost. +resources_moveDestNotSub = Cannot move ''{0}''. Destination should not be under source''s hierarchy. +resources_moveMeta = Error moving metadata area from {0} to {1}. +resources_moveNotMet = Move requirements not met. +resources_moveNotProject = Cannot move ''{0}'' to ''{1}''. Source must be a project. +resources_moveProblem = Problems encountered while moving resources. +resources_moveRoot = Cannot move the workspace root. +resources_moving = Moving ''{0}''. +resources_moving_0 = Moving. +resources_mustBeAbsolute = Path ''{0}'' must be absolute. +resources_mustBeLocal = Resource ''{0}'' is not local. +resources_mustBeOpen = Resource ''{0}'' is not open. +resources_mustExist = Resource ''{0}'' does not exist. +resources_mustNotExist = Resource ''{0}'' already exists. +resources_nameEmpty = Name cannot be empty. +resources_nameNull = Name must not be null. +resources_natureClass = Missing project nature class for ''{0}''. +resources_natureDeconfig = Error deconfiguring nature: {0}. +resources_natureExtension = Missing project nature extension for {0}. +resources_natureFormat = Project nature {0} does not specify a runtime attribute. +resources_natureImplement = Project nature {0} does not implement IProjectNature. +resources_notChild = Resource ''{0}'' is not a child of ''{1}''. +resources_oneValidator = There must be exactly 0 or 1 validator extensions defined in the fileModificationValidator extension point. +resources_oneHook = There must be exactly 0 or 1 hook extensions defined in the moveDeleteHook extension point. +resources_oneTeamHook = There must be exactly 0 or 1 hook extensions defined in the teamHook extension point. +resources_opening_1 = Opening ''{0}''. +resources_overlapWorkspace = {0} overlaps the workspace location: {1} +resources_overlapProject = {0} overlaps the location of another project: ''{1}'' +resources_pathNull = Paths must not be null. +resources_projectDesc = Problems encountered while setting project description. +resources_projectDescSync = Could not set the project description for ''{0}'' because the project description file (.project) is out of sync with the file system. +resources_projectMustNotBeOpen = Project must not be open. +resources_projectPath = Path for project must have only one segment. +resources_pruningHistory = Pruning history. +resources_reading = Reading. +resources_readingSnap = Reading snapshot. +resources_readingEncoding = Could not read encoding settings. +resources_readMarkers = Failure while reading markers, the marker file was corrupt. Some markers may be lost. +resources_readMeta = Could not read metadata for ''{0}''. +resources_readMetaWrongVersion = Could not read metadata for ''{0}''. Unexpected version: {1}. +resources_readOnly = Resource ''{0}'' is read-only. +resources_readOnly2 = Cannot edit read-only resources. +resources_readProjectMeta = Failed to read the project description file (.project) for ''{0}''. The file has been changed on disk, and it now contains invalid information. The project will not function properly until the description file is restored to a valid state. +resources_readProjectTree = Problems reading project tree. +resources_readSync = Errors reading sync info file: {0}. +resources_readWorkspaceMeta = Could not read workspace metadata. +resources_readWorkspaceMetaValue = Invalid attribute value in workspace metadata: {0}. Value will be ignored. +resources_readWorkspaceSnap = Problems reading workspace tree snapshot. +resources_readWorkspaceTree = Problems reading workspace tree. +resources_refreshing = Refreshing ''{0}''. +resources_refreshingRoot = Refreshing workspace. +resources_resetMarkers = Could not reset markers snapshot file. +resources_resetSync = Could not reset sync info snapshot file. +resources_resourcePath = Invalid path for resource ''{0}''. Must include project and resource name. +resources_saveOp = Save cannot be called from inside an operation. +resources_saveProblem = Problems occurred during save. +resources_saveWarnings = Save operation warnings. +resources_saving_0 = Saving workspace. +resources_savingEncoding = Could not save encoding settings. +resources_setDesc = Setting project description. +resources_setLocal = Setting resource local flag. +resources_settingCharset = Setting character set for resource ''{0}''. +resources_settingDefaultCharsetContainer = Setting default character set for resource ''{0}''. +resources_settingContents = Setting contents for ''{0}''. +resources_settingDerivedFlag = Setting derived flag for resource ''{0}''. +resources_shutdown = Workspace was not properly initialized or has already shutdown. +resources_shutdownProblems = Problem on shutdown. +resources_snapInit = Could not initialize snapshot file. +resources_snapRead = Could not read snapshot file. +resources_snapRequest = Snapshot requested. +resources_snapshot = Periodic workspace save. +resources_startupProblems = Workspace restored, but some problems occurred. +resources_touch = Touching resource ''{0}''. +resources_updating = Updating workspace +resources_updatingEncoding = Problems encountered while updating encoding settings. +resources_workspaceClosed = Workspace is closed. +resources_workspaceOpen = The workspace is already open. +resources_writeMeta = Could not write metadata for ''{0}''. +resources_writeWorkspaceMeta = Could not write workspace metadata ''{0}''. +resources_errorResourceIsFiltered=The resource will be filtered out by its parent resource filters + +synchronizer_partnerNotRegistered = Sync partner: {0} not registered with the synchronizer. + +### URL +url_badVariant = Unsupported \"platform:\" protocol variation {0}. +url_couldNotResolve_projectDoesNotExist = Project ''{0}'' does not exist. Could not resolve URL: {1}. +url_couldNotResolve_URLProtocolHandlerCanNotResolveURL = A protocol handler does not exist or can not resolve URL ''{0}'' into URL with file protocol. Could not resolve URL: {1}. +url_couldNotResolve_resourceLocationCanNotBeDetermined = Resource location ''{0}'' can not be determined. Could not resolve URL: {1}. + +### utils +utils_clone = Clone not supported. +utils_stringJobName = Compacting resource model + +### watson +watson_elementNotFound = Element not found: {0}. +watson_illegalSubtree = Illegal subtree passed to createSubtree(). +watson_immutable = Attempt to modify an immutable tree. +watson_noModify = Cannot modify implicit root node. +watson_nullArg = Null argument to {0}. +watson_unknown = Unknown format. + +### auto-refresh win32 native +WM_beginTask = finding out of sync resources +WM_jobName = Win32 refresh daemon +WM_errors = Problems occurred refreshing resources +WM_nativeErr = Problem occurred in auto-refresh native code: {0}. +WM_errCloseHandle = Problem closing native refresh handle: {0}. +WM_errCreateHandle = Problem creating handle for {0}, code: {0}. +WM_errFindChange = Problem finding next change, code: {0} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java new file mode 100644 index 0000000000..f21af4e383 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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.watson; + +/** + * This is what you would expect for an element tree comparator. + * Clients of the element tree that want specific comparison behaviour + * must define their own element comparator (without subclassing or + * otherwise extending this comparator). Internal element tree operations + * rely on the behaviour of this type, and the ElementTree maintainer + * reserves the right to change its behaviour as necessary. + */ +public final class DefaultElementComparator implements IElementComparator { + private static DefaultElementComparator singleton; + + /** + * Force clients to use the singleton + */ + protected DefaultElementComparator() { + super(); + } + + /** + * Returns the type of change. + */ + @Override + public int compare(Object oldInfo, Object newInfo) { + if (oldInfo == null && newInfo == null) + return 0; + if (oldInfo == null || newInfo == null) + return 1; + return testEquality(oldInfo, newInfo) ? 0 : 1; + } + + /** + * Returns the singleton instance + */ + public static IElementComparator getComparator() { + if (singleton == null) { + singleton = new DefaultElementComparator(); + } + return singleton; + } + + /** + * Makes a comparison based on equality + */ + protected boolean testEquality(Object oldInfo, Object newInfo) { + if (oldInfo == null && newInfo == null) + return true; + if (oldInfo == null || newInfo == null) + return false; + + return oldInfo.equals(newInfo); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java new file mode 100644 index 0000000000..f72a5c2d27 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java @@ -0,0 +1,732 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +import java.util.HashMap; +import org.eclipse.core.internal.dtree.*; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.StringPool; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * An ElementTree can be viewed as a generic rooted tree that stores + * a hierarchy of elements. An element in the tree consists of a + * (name, data, children) 3-tuple. The name can be any String, and + * the data can be any Object. The children are a collection of zero + * or more elements that logically fall below their parent in the tree. + * The implementation makes no guarantees about the ordering of children. + * + * Elements in the tree are referenced by a key that consists of the names + * of all elements on the path from the root to that element in the tree. + * For example, if root node "a" has child "b", which has child "c", element + * "c" can be referenced in the tree using the key (/a/b/c). Keys are represented + * using IPath objects, where the Paths are relative to the root element of the + * tree. + * @see IPath + * + * Each ElementTree has a single root element that is created implicitly and + * is always present in any tree. This root corresponds to the key (/), + * or the singleton Path.ROOT. The root element cannot be created + * or deleted, and its data and name cannot be set. The root element's children + * however can be modified (added, deleted, etc). The root path can be obtained + * using the getRoot() method. + * + * ElementTrees are modified in generations. The method newEmptyDelta() + * returns a new tree generation that can be modified arbitrarily by the user. + * For the purpose of explanation, we call such a tree "active". + * When the method immutable() is called, that tree generation is + * frozen, and can never again be modified. A tree must be immutable before + * a new tree generation can start. Since all ancestor trees are immutable, + * different active trees can have ancestors in common without fear of + * thread corruption problems. + * + * Internally, any single tree generation is simply stored as the + * set of changes between itself and its most recent ancestor (its parent). + * This compact delta representation allows chains of element trees to + * be created at relatively low cost. Clients of the ElementTree can + * instantaneously "undo" sets of changes by navigating up to the parent + * tree using the getParent() method. + * + * Although the delta representation is compact, extremely long delta + * chains make for a large structure that is potentially slow to query. + * For this reason, the client is encouraged to minimize delta chain + * lengths using the collapsing(int) and makeComplete() + * methods. The getDeltaDepth() method can be used to + * discover the length of the delta chain. The entire delta chain can + * also be re-oriented in terms of the current element tree using the + * reroot() operation. + * + * Classes are also available for tree serialization and navigation. + * @see ElementTreeReader + * @see ElementTreeWriter + * @see ElementTreeIterator + * + * Finally, why are ElementTrees in a package called "watson"? + * - "It's ElementTree my dear Watson, ElementTree." + */ +public class ElementTree { + protected DeltaDataTree tree; + protected IElementTreeData userData; + + private class ChildIDsCache { + ChildIDsCache(IPath path, IPath[] childPaths) { + this.path = path; + this.childPaths = childPaths; + } + + IPath path; + IPath[] childPaths; + } + + private volatile ChildIDsCache childIDsCache = null; + + private volatile DataTreeLookup lookupCache = null; + + private volatile DataTreeLookup lookupCacheIgnoreCase = null; + + private static int treeCounter = 0; + private int treeStamp; + + /** + * Creates a new empty element tree. + */ + public ElementTree() { + initialize(new DeltaDataTree()); + } + + /** + * Creates an element tree given its internal node representation. + */ + protected ElementTree(DataTreeNode rootNode) { + initialize(rootNode); + } + + /** + * Creates a new element tree with the given data tree as its representation. + */ + protected ElementTree(DeltaDataTree tree) { + initialize(tree); + } + + /** + * Creates a new empty delta element tree having the + * given tree as its parent. + */ + protected ElementTree(ElementTree parent) { + if (!parent.isImmutable()) { + parent.immutable(); + } + + /* copy the user data forward */ + IElementTreeData data = parent.getTreeData(); + if (data != null) { + userData = (IElementTreeData) data.clone(); + } + + initialize(parent.tree.newEmptyDeltaTree()); + } + + /** + * 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 + * element trees created by newEmptyDelta()/immutable(). + * + *

          This element tree must be immutable at the start of this operation, + * and will be immutable afterwards. + * @return this tree. + */ + public synchronized ElementTree collapseTo(ElementTree parent) { + Assert.isTrue(tree.isImmutable()); + if (this == parent) { + //already collapsed + return this; + } + //collapse my tree to be a forward delta of the parent's tree. + tree.collapseTo(parent.tree, DefaultElementComparator.getComparator()); + return this; + } + + /** + * Creates the indicated element and sets its element info. + * The parent element must be present, otherwise an IllegalArgumentException + * is thrown. If the indicated element is already present in the tree, + * its element info is replaced and any existing children are + * deleted. + * + * @param key element key + * @param data element data, or null + */ + public synchronized void createElement(IPath key, Object data) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return; + + // Clear the child IDs cache in case it's referring to this parent. This is conservative. + childIDsCache = null; + + IPath parent = key.removeLastSegments(1); + try { + tree.createChild(parent, key.lastSegment(), data); + } catch (ObjectNotFoundException e) { + elementNotFound(parent); + } + // Set the lookup to be this newly created object. + lookupCache = DataTreeLookup.newLookup(key, true, data, true); + lookupCacheIgnoreCase = null; + } + + /** + * Creates or replaces the subtree below the given path with + * the given tree. The subtree can only have one child below + * the root, which will become the node specified by the given + * key in this tree. + * + * @param key The path of the new subtree in this tree. + * @see #getSubtree(IPath) + */ + public synchronized void createSubtree(IPath key, ElementTree subtree) { + /* don't allow creating subtrees at the root */ + if (key.isRoot()) { + throw new IllegalArgumentException(Messages.watson_noModify); + } + + // Clear the child IDs cache in case it's referring to this parent. + // This is conservative. + childIDsCache = null; + // Clear the lookup cache, in case the element being created is the same + // as for the last lookup. + lookupCache = lookupCacheIgnoreCase = null; + try { + /* don't copy the implicit root node of the subtree */ + IPath[] children = subtree.getChildren(subtree.getRoot()); + if (children.length != 1) { + throw new IllegalArgumentException(Messages.watson_illegalSubtree); + } + + /* get the subtree for the specified key */ + DataTreeNode node = (DataTreeNode) subtree.tree.copyCompleteSubtree(children[0]); + + /* insert the subtree in this tree */ + tree.createSubtree(key, node); + + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + + /** + * Deletes the indicated element and its descendents. + * The element must be present. + */ + public synchronized void deleteElement(IPath key) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return; + + // Clear the child IDs cache in case it's referring to this parent. + // This is conservative. + childIDsCache = null; + // Clear the lookup cache, in case the element being deleted is the same + // as for the last lookup. + lookupCache = lookupCacheIgnoreCase = null; + try { + tree.deleteChild(key.removeLastSegments(1), key.lastSegment()); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + + /** + * Complains that an element was not found + */ + protected void elementNotFound(IPath key) { + throw new IllegalArgumentException(NLS.bind(Messages.watson_elementNotFound, key)); + } + + /** + * Given an array of element trees, returns the index of the + * oldest tree. The oldest tree is the tree such that no + * other tree in the array is a descendent of that tree. + * Note that this counter-intuitive concept of oldest is based on the + * ElementTree orientation such that the complete tree is always the + * newest tree. + */ + public static int findOldest(ElementTree[] trees) { + + /* first put all the trees in a hashtable */ + HashMap candidates = new HashMap<>((int) (trees.length * 1.5 + 1)); + for (int i = 0; i < trees.length; i++) { + candidates.put(trees[i], trees[i]); + } + + /* keep removing parents until only one tree remains */ + ElementTree oldestSoFar = null; + while (candidates.size() > 0) { + /* get a new candidate */ + ElementTree current = candidates.values().iterator().next(); + + /* remove this candidate from the table */ + candidates.remove(current); + + /* remove all of this element's parents from the list of candidates*/ + ElementTree parent = current.getParent(); + + /* walk up chain until we hit the root or a tree we have already tested */ + while (parent != null && parent != oldestSoFar) { + candidates.remove(parent); + parent = parent.getParent(); + } + + /* the current candidate is the oldest tree seen so far */ + oldestSoFar = current; + + /* if the table is now empty, we have a winner */ + } + Assert.isNotNull(oldestSoFar); + + /* return the appropriate index */ + for (int i = 0; i < trees.length; i++) { + if (trees[i] == oldestSoFar) { + return i; + } + } + Assert.isTrue(false, "Should not get here"); //$NON-NLS-1$ + return -1; + } + + /** + * Returns the number of children of the element + * specified by the given path. + * The given element must be present in this tree. + */ + public synchronized int getChildCount(IPath key) { + Assert.isNotNull(key); + return getChildIDs(key).length; + } + + /** + * Returns the IDs of the children of the specified element. + * If the specified element is null, returns the root element path. + */ + protected IPath[] getChildIDs(IPath key) { + ChildIDsCache cache = childIDsCache; // Grab it in case it's replaced concurrently. + if (cache != null && cache.path == key) { + return cache.childPaths; + } + try { + if (key == null) + return new IPath[] {tree.rootKey()}; + IPath[] children = tree.getChildren(key); + childIDsCache = new ChildIDsCache(key, children); // Cache the result + return children; + } catch (ObjectNotFoundException e) { + elementNotFound(key); + return null; // can't get here + } + } + + /** + * Returns the paths of the children of the element + * specified by the given path. + * The given element must be present in this tree. + */ + public synchronized IPath[] getChildren(IPath key) { + Assert.isNotNull(key); + return getChildIDs(key); + } + + /** + * Returns the internal data tree. + */ + public DeltaDataTree getDataTree() { + return tree; + } + + /** + * Returns the element data for the given element identifier. + * The given element must be present in this tree. + */ + public synchronized Object getElementData(IPath key) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return null; + DataTreeLookup lookup = lookupCache; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) + lookupCache = lookup = tree.lookup(key); + if (lookup.isPresent) + return lookup.data; + elementNotFound(key); + return null; // can't get here + } + + /** + * Returns the element data for the given element identifier. + * The given element must be present in this tree. + */ + public synchronized Object getElementDataIgnoreCase(IPath key) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return null; + DataTreeLookup lookup = lookupCacheIgnoreCase; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) + lookupCacheIgnoreCase = lookup = tree.lookupIgnoreCase(key); + if (lookup.isPresent) + return lookup.data; + elementNotFound(key); + return null; // can't get here + } + + /** + * Returns the names of the children of the specified element. + * The specified element must exist in the tree. + * If the specified element is null, returns the root element path. + */ + public synchronized String[] getNamesOfChildren(IPath key) { + try { + if (key == null) + return new String[] {""}; //$NON-NLS-1$ + return tree.getNamesOfChildren(key); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + return null; // can't get here + } + } + + /** + * Returns the parent tree, or null if there is no parent. + */ + public ElementTree getParent() { + DeltaDataTree parentTree = tree.getParent(); + if (parentTree == null) { + return null; + } + // The parent ElementTree is stored as the node data of the parent DeltaDataTree, + // to simplify canonicalization in the presence of rerooting. + return (ElementTree) parentTree.getData(tree.rootKey()); + } + + /** + * Returns the root node of this tree. + */ + public IPath getRoot() { + return getChildIDs(null)[0]; + } + + /** + * Returns the subtree rooted at the given key. In the resulting tree, + * the implicit root node (designated by Path.ROOT), has a single child, + * which is the node specified by the given key in this tree. + * + * The subtree must be present in this tree. + * + * @see #createSubtree(IPath, ElementTree) + */ + public ElementTree getSubtree(IPath key) { + /* the subtree of the root of this tree is just this tree */ + if (key.isRoot()) { + return this; + } + try { + DataTreeNode elementNode = (DataTreeNode) tree.copyCompleteSubtree(key); + return new ElementTree(elementNode); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + return null; + } + } + + /** + * Returns the user data associated with this tree. + */ + public IElementTreeData getTreeData() { + return userData; + } + + /** + * Returns true if there have been changes in the tree between the two + * given layers. The two must be related and new must be newer than old. + * That is, new must be an ancestor of old. + */ + public static boolean hasChanges(ElementTree newLayer, ElementTree oldLayer, IElementComparator comparator, boolean inclusive) { + // if any of the layers are null, assume that things have changed + if (newLayer == null || oldLayer == null) + return true; + if (newLayer == oldLayer) + return false; + //if the tree data has changed, then the tree has changed + if (comparator.compare(newLayer.getTreeData(), oldLayer.getTreeData()) != IElementComparator.K_NO_CHANGE) + return true; + + // The tree structure has the top layer(s) (i.e., tree) parentage pointing down to a complete + // layer whose parent is null. The bottom layers (i.e., operationTree) point up to the + // common complete layer whose parent is null. The complete layer moves up as + // changes happen. To see if any changes have happened, we should consider only + // layers whose parent is not null. That is, skip the complete layer as it will clearly not be + // empty. + + // look down from the current layer (always inclusive) if the top layer is mutable + ElementTree stopLayer = null; + if (newLayer.isImmutable()) + // if the newLayer is immutable, the tree structure all points up so ensure that + // when searching up, we stop at newLayer (inclusive) + stopLayer = newLayer.getParent(); + else { + ElementTree layer = newLayer; + while (layer != null && layer.getParent() != null) { + if (!layer.getDataTree().isEmptyDelta()) + return true; + layer = layer.getParent(); + } + } + + // look up from the layer at which we started to null or newLayer's parent (variably inclusive) + // depending on whether newLayer is mutable. + ElementTree layer = inclusive ? oldLayer : oldLayer.getParent(); + while (layer != null && layer.getParent() != stopLayer) { + if (!layer.getDataTree().isEmptyDelta()) + return true; + layer = layer.getParent(); + } + // didn't find anything that changed + return false; + } + + /** + * Makes this tree immutable (read-only); ignored if it is already + * immutable. + */ + public synchronized void immutable() { + if (!tree.isImmutable()) { + tree.immutable(); + /* need to clear the lookup cache since it reports whether results were found + in the topmost delta, and the order of deltas is changing */ + lookupCache = lookupCacheIgnoreCase = null; + /* reroot the delta chain at this tree */ + tree.reroot(); + } + } + + /** + * Returns true if this element tree includes an element with the given + * key, false otherwise. + */ + public synchronized boolean includes(IPath key) { + DataTreeLookup lookup = lookupCache; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) { + lookupCache = lookup = tree.lookup(key); + } + return lookup.isPresent; + } + + /** + * Returns true if this element tree includes an element with the given + * key, ignoring the case of the key, and false otherwise. + */ + public boolean includesIgnoreCase(IPath key) { + DataTreeLookup lookup = lookupCacheIgnoreCase; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) { + lookupCacheIgnoreCase = lookup = tree.lookupIgnoreCase(key); + } + return lookup.isPresent; + } + + protected void initialize(DataTreeNode rootNode) { + /* create the implicit root node */ + initialize(new DeltaDataTree(new DataTreeNode(null, null, new AbstractDataTreeNode[] {rootNode}))); + } + + protected void initialize(DeltaDataTree newTree) { + // Keep this element tree as the data of the root node. + // Useful for canonical results for ElementTree.getParent(). + // see getParent(). + treeStamp = treeCounter++; + newTree.setData(newTree.rootKey(), this); + this.tree = newTree; + } + + /** + * Returns whether this tree is immutable. + */ + public boolean isImmutable() { + return tree.isImmutable(); + } + + /** + * Merges a chain of deltas for a certain subtree to this tree. + * If this tree has any data in the specified subtree, it will + * be overwritten. The receiver tree must be open, and it will + * be made immutable during the merge operation. The trees in the + * provided array will be replaced by new trees that have been + * merged into the receiver's delta chain. + * + * @param path The path of the subtree chain to merge + * @param trees The chain of trees to merge. The trees can be + * in any order, but they must all form a simple ancestral chain. + * @return A new open tree with the delta chain merged in. + */ + public ElementTree mergeDeltaChain(IPath path, ElementTree[] trees) { + if (path == null || trees == null) { + throw new IllegalArgumentException(NLS.bind(Messages.watson_nullArg, "ElementTree.mergeDeltaChain")); //$NON-NLS-1$ + } + + /* The tree has to be open */ + if (isImmutable()) { + throw new IllegalArgumentException(Messages.watson_immutable); + } + ElementTree current = this; + if (trees.length > 0) { + /* find the oldest tree to be merged */ + ElementTree toMerge = trees[findOldest(trees)]; + + /* merge the trees from oldest to newest */ + while (toMerge != null) { + if (path.isRoot()) { + //copy all the children + IPath[] children = toMerge.getChildren(Path.ROOT); + for (int i = 0; i < children.length; i++) { + current.createSubtree(children[i], toMerge.getSubtree(children[i])); + } + } else { + //just copy the specified node + current.createSubtree(path, toMerge.getSubtree(path)); + } + current.immutable(); + + /* replace the tree in the array */ + /* we have to go through all trees because there may be duplicates */ + for (int i = 0; i < trees.length; i++) { + if (trees[i] == toMerge) { + trees[i] = current; + } + } + current = current.newEmptyDelta(); + toMerge = toMerge.getParent(); + } + } + return current; + } + + /** + * Creates a new element tree which is represented as a delta on this one. + * Initially they have the same content. Subsequent changes to the new + * tree will not affect this one. + */ + public synchronized ElementTree newEmptyDelta() { + // Don't want old trees hanging onto cached infos. + lookupCache = lookupCacheIgnoreCase = null; + return new ElementTree(this); + } + + /** + * Returns a mutable copy of the element data for the given path. + * This copy will be held onto in the most recent delta. + * ElementTree data MUST implement the IElementTreeData interface + * for this method to work. If the data does not define that interface + * this method will fail. + */ + public synchronized Object openElementData(IPath key) { + Assert.isTrue(!isImmutable()); + + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return null; + DataTreeLookup lookup = lookupCache; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) { + lookupCache = lookup = tree.lookup(key); + } + if (lookup.isPresent) { + if (lookup.foundInFirstDelta) + return lookup.data; + /** + * The node has no data in the most recent delta. + * Pull it up to the present delta by setting its data with a clone. + */ + IElementTreeData oldData = (IElementTreeData) lookup.data; + if (oldData != null) { + try { + Object newData = oldData.clone(); + tree.setData(key, newData); + lookupCache = lookupCacheIgnoreCase = null; + return newData; + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + } else { + elementNotFound(key); + } + return null; + } + + /** + * Sets the element for the given element identifier. + * The given element must be present in this tree. + * @param key element identifier + * @param data element info, or null + */ + public synchronized void setElementData(IPath key, Object data) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return; + + Assert.isNotNull(key); + // Clear the lookup cache, in case the element being modified is the same + // as for the last lookup. + lookupCache = lookupCacheIgnoreCase = null; + try { + tree.setData(key, data); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + + /** + * Sets the user data associated with this tree. + */ + public void setTreeData(IElementTreeData data) { + userData = data; + } + + /* (non-Javadoc) + * Method declared on IStringPoolParticipant + */ + public void shareStrings(StringPool set) { + tree.storeStrings(set); + } + + /** + * Returns a string representation of this element tree's + * structure suitable for debug purposes. + */ + public String toDebugString() { + final StringBuffer buffer = new StringBuffer("\n"); //$NON-NLS-1$ + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree aTree, IPathRequestor elementID, Object elementContents) { + buffer.append(elementID.requestPath() + " " + elementContents + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + return true; + } + }; + new ElementTreeIterator(this, Path.ROOT).iterate(visitor); + return buffer.toString(); + } + + @Override + public String toString() { + return "ElementTree(" + treeStamp + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java new file mode 100644 index 0000000000..e433df07b5 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * 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.watson; + +import org.eclipse.core.internal.dtree.AbstractDataTreeNode; +import org.eclipse.core.internal.dtree.DataTreeNode; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; + +/** + * A class for performing operations on each element in an element tree. + * For example, this can be used to print the contents of a tree. + *

          + * When creating an ElementTree iterator, an element tree and root path must be + * supplied. When the iterate() method is called, a visitor object + * must be provided. The visitor is called once for each node of the tree. For + * each node, the visitor is passed the entire tree, the object in the tree at + * that node, and a callback for requesting the full path of that node. + *

          + * Example: +

          + // printing a crude representation of the poster child
          + IElementContentVisitor visitor=
          +     new IElementContentVisitor() {
          +   public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
          +     System.out.println(requestor.requestPath() + " -> " + elementContents);
          +     return true;
          +   }
          + });
          + ElementTreeIterator iterator = new ElementTreeIterator(tree, Path.ROOT);
          + iterator.iterate(visitor);
          + 
          + */ +public class ElementTreeIterator implements IPathRequestor { + //for path requestor + private String[] segments = new String[10]; + private int nextFreeSegment; + + /* the tree being visited */ + private ElementTree tree; + + /* the root of the subtree to visit */ + private IPath path; + + /* the immutable data tree being visited */ + private DataTreeNode treeRoot; + + /** + * Creates a new element tree iterator for visiting the given tree starting + * at the given path. + */ + public ElementTreeIterator(ElementTree tree, IPath path) { + this.tree = tree; + this.path = path; + //treeRoot can be null if deleted concurrently + //must copy the tree while owning the tree's monitor to prevent concurrent deletion while creating visitor's copy + synchronized (tree) { + treeRoot = (DataTreeNode) tree.getDataTree().safeCopyCompleteSubtree(path); + } + } + + /** + * Iterates through the given element tree and visit each element (node) + * passing in the element's ID and element object. + */ + private void doIteration(DataTreeNode node, IElementContentVisitor visitor) { + //push the name of this node to the requestor stack + if (nextFreeSegment >= segments.length) { + grow(); + } + segments[nextFreeSegment++] = node.getName(); + + //do the visit + if (visitor.visitElement(tree, this, node.getData())) { + //recurse + AbstractDataTreeNode[] children = node.getChildren(); + int len = children.length; + for (int i = 0; i < len; i++) { + doIteration((DataTreeNode) children[i], visitor); + } + } + + //pop the segment from the requestor stack + nextFreeSegment--; + if (nextFreeSegment < 0) + nextFreeSegment = 0; + } + + /** + * Method grow. + */ + private void grow() { + //grow the segments array + int oldLen = segments.length; + String[] newPaths = new String[oldLen * 2]; + System.arraycopy(segments, 0, newPaths, 0, oldLen); + segments = newPaths; + } + + /** + * Iterates through this iterator's tree and visits each element in the + * subtree rooted at the given path. The visitor is passed each element's + * data and a request callback for obtaining the path. + */ + public void iterate(IElementContentVisitor visitor) { + if (path.isRoot()) { + //special visit for root element to use special treeData + if (visitor.visitElement(tree, this, tree.getTreeData())) { + if (treeRoot == null) + return; + AbstractDataTreeNode[] children = treeRoot.getChildren(); + int len = children.length; + for (int i = 0; i < len; i++) { + doIteration((DataTreeNode) children[i], visitor); + } + } + } else { + if (treeRoot == null) + return; + push(path, path.segmentCount() - 1); + doIteration(treeRoot, visitor); + } + } + + /** + * Push the first "toPush" segments of this path. + */ + private void push(IPath pathToPush, int toPush) { + if (toPush <= 0) + return; + for (int i = 0; i < toPush; i++) { + if (nextFreeSegment >= segments.length) { + grow(); + } + segments[nextFreeSegment++] = pathToPush.segment(i); + } + } + + @Override + public String requestName() { + if (nextFreeSegment == 0) + return ""; //$NON-NLS-1$ + return segments[nextFreeSegment - 1]; + } + + @Override + public IPath requestPath() { + if (nextFreeSegment == 0) + return Path.ROOT; + int length = nextFreeSegment; + for (int i = 0; i < nextFreeSegment; i++) { + length += segments[i].length(); + } + StringBuffer pathBuf = new StringBuffer(length); + for (int i = 0; i < nextFreeSegment; i++) { + pathBuf.append('/'); + pathBuf.append(segments[i]); + } + return new Path(null, pathBuf.toString()); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.java new file mode 100644 index 0000000000..0947aac02c --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.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 + * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +import java.io.*; +import org.eclipse.core.internal.dtree.DataTreeReader; +import org.eclipse.core.internal.dtree.IDataFlattener; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.runtime.*; + +/** ElementTreeReader is the standard implementation + * of an element tree serialization reader. + * + *

          Subclasses of this reader read can handle current and various + * known old formats of a saved element tree, and dispatch internally + * to an appropriate reader. + * + *

          The reader has an IElementInfoFactory, + * which it consults for the schema and for creating + * and reading element infos. + * + *

          Element tree readers are thread-safe; several + * threads may share a single reader provided, of course, + * that the IElementInfoFactory is thread-safe. + */ +public class ElementTreeReader { + /** The element info factory. + */ + protected IElementInfoFlattener elementInfoFlattener; + + /** + * For reading and writing delta trees + */ + protected DataTreeReader dataTreeReader; + + /** + * Constructs a new element tree reader that works for + * the given element info flattener. + */ + public ElementTreeReader(final IElementInfoFlattener factory) { + Assert.isNotNull(factory); + elementInfoFlattener = factory; + + /* wrap the IElementInfoFlattener in an IDataFlattener */ + IDataFlattener f = new IDataFlattener() { + @Override + public void writeData(IPath path, Object data, DataOutput output) { + //not needed + } + + @Override + public Object readData(IPath path, DataInput input) throws IOException { + //never read the root node of an ElementTree + //this node is reserved for the parent backpointer + if (!Path.ROOT.equals(path)) + return factory.readElement(path, input); + return null; + } + }; + dataTreeReader = new DataTreeReader(f); + } + + /** + * Returns the appropriate reader for the given version. + */ + public ElementTreeReader getReader(int formatVersion) throws IOException { + if (formatVersion == 1) + return new ElementTreeReaderImpl_1(elementInfoFlattener); + throw new IOException(Messages.watson_unknown); + } + + /** + * Reads an element tree delta from the input stream, and + * reconstructs it as a delta on the given tree. + */ + public ElementTree readDelta(ElementTree completeTree, DataInput input) throws IOException { + /* Dispatch to the appropriate reader. */ + ElementTreeReader realReader = getReader(readNumber(input)); + return realReader.readDelta(completeTree, input); + } + + /** + * Reads a chain of ElementTrees from the given input stream. + * @return A chain of ElementTrees, where the first tree in the list is + * complete, and all other trees are deltas on the previous tree in the list. + */ + public ElementTree[] readDeltaChain(DataInput input) throws IOException { + return readDeltaChain(input, ""); //$NON-NLS-1$ + } + + /** + * Reads a chain of ElementTrees from the given input stream. + * @param input the input stream to read from. + * @param newProjectName a new name to use for the root node of the + * tree being read, or the empty String ("") to read the tree + * from the given input unchanged. + * @return A chain of ElementTrees, where the first tree in the list is + * complete, and all other trees are deltas on the previous tree in the list. + s */ + public ElementTree[] readDeltaChain(DataInput input, String newProjectName) throws IOException { + /* Dispatch to the appropriate reader. */ + ElementTreeReader realReader = getReader(readNumber(input)); + return realReader.readDeltaChain(input, newProjectName); + } + + /** + * 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 static int readNumber(DataInput input) 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 an element tree from the input stream and returns it. + * This method actually just dispatches to the appropriate reader + * depending on the stream version id. + */ + public ElementTree readTree(DataInput input) throws IOException { + return readTree(input, ""); //$NON-NLS-1$ + } + + /** + * Reads an element tree from the input stream and returns it. + * This method actually just dispatches to the appropriate reader + * depending on the stream version id. + * @param input the input stream to read from. + * @param newProjectName a new name to use for the root node of the + * tree being read, or the empty String ("") to read the tree + * from the given input unchanged. + * @return the requested ElementTree. + */ + public ElementTree readTree(DataInput input, String newProjectName) throws IOException { + /* Dispatch to the appropriate reader. */ + ElementTreeReader realReader = getReader(readNumber(input)); + return realReader.readTree(input, newProjectName); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java new file mode 100644 index 0000000000..a8bead1266 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * 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) - [305718] Allow reading snapshot into renamed project + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +import java.io.DataInput; +import java.io.IOException; +import org.eclipse.core.internal.dtree.DeltaDataTree; + +/** ElementTreeReader_1 is an implementation + * of the ElementTreeReader for format version 1. + * + *

          Instances of this reader read only format 1 + * of a saved element tree (they do not deal with + * compatibility issues). + * + * @see ElementTreeReader + */ +/* package */class ElementTreeReaderImpl_1 extends ElementTreeReader { + + /** + * Constructs a new element tree reader that works for + * the given element info factory. + */ + ElementTreeReaderImpl_1(IElementInfoFlattener factory) { + super(factory); + } + + /** + * Reads an element tree delta from the input stream, and + * reconstructs it as a delta on the given tree. + */ + @Override + public ElementTree readDelta(ElementTree parentTree, DataInput input) throws IOException { + DeltaDataTree complete = parentTree.getDataTree(); + DeltaDataTree delta = dataTreeReader.readTree(complete, input, ""); //$NON-NLS-1$ + + //if the delta is empty, just return the parent + if (delta.isEmptyDelta()) + return parentTree; + + ElementTree tree = new ElementTree(delta); + + //copy the user data forward + IElementTreeData data = parentTree.getTreeData(); + if (data != null) { + tree.setTreeData((IElementTreeData) data.clone()); + } + + //make the underlying data tree immutable + //can't call immutable() on the ElementTree because + //this would attempt to reroot. + delta.immutable(); + return tree; + } + + @Override + public ElementTree[] readDeltaChain(DataInput input, String newProjectName) throws IOException { + /* read the number of trees */ + int treeCount = readNumber(input); + ElementTree[] results = new ElementTree[treeCount]; + + if (treeCount <= 0) { + return results; + } + + /* read the sort order */ + int[] order = new int[treeCount]; + for (int i = 0; i < treeCount; i++) { + order[i] = readNumber(input); + } + + /* read the complete tree */ + results[order[0]] = super.readTree(input, newProjectName); + + /* reconstitute each of the remaining trees from their written deltas */ + for (int i = 1; i < treeCount; i++) { + results[order[i]] = super.readDelta(results[order[i - 1]], input); + } + + return results; + } + + @Override + public ElementTree readTree(DataInput input, String newProjectName) throws IOException { + + /* The format version number has already been consumed + * by ElementTreeReader#readFrom. + */ + ElementTree result = new ElementTree(dataTreeReader.readTree(null, input, newProjectName)); + return result; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java new file mode 100644 index 0000000000..a9352f27a1 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java @@ -0,0 +1,242 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +import java.io.*; +import java.util.*; +import org.eclipse.core.internal.dtree.*; +import org.eclipse.core.runtime.*; + +/** ElementTreeWriter flattens an ElementTree + * onto a data output stream. + * + *

          This writer generates the most up-to-date format + * of a saved element tree (cf. readers, which must usually also + * deal with backward compatibility issues). The flattened + * representation always includes a format version number. + * + *

          The writer has an IElementInfoFactory, + * which it consults for writing element infos. + * + *

          Element tree writers are thread-safe; several + * threads may share a single writer. + * + */ +public class ElementTreeWriter { + /** + * The current format version number. + */ + public static final int CURRENT_FORMAT = 1; + + /** + * Constant representing infinite depth + */ + public static final int D_INFINITE = DataTreeWriter.D_INFINITE; + + /** + * For writing DeltaDataTrees + */ + protected DataTreeWriter dataTreeWriter; + + /** + * Constructs a new element tree writer that works for + * the given element info flattener. + */ + public ElementTreeWriter(final IElementInfoFlattener flattener) { + + /* wrap the IElementInfoFlattener in an IDataFlattener */ + IDataFlattener f = new IDataFlattener() { + @Override + public void writeData(IPath path, Object data, DataOutput output) throws IOException { + // never write the root node of an ElementTree + //because it contains the parent backpointer. + if (!Path.ROOT.equals(path)) { + flattener.writeElement(path, data, output); + } + } + + @Override + public Object readData(IPath path, DataInput input) { + return null; + } + }; + dataTreeWriter = new DataTreeWriter(f); + } + + /** + * Sorts the given array of trees so that the following rules are true: + * - The first tree has no parent + * - No tree has an ancestor with a greater index in the array. + * If there are no missing parents in the given trees array, this means + * that in the resulting array, the i'th tree's parent will be tree i-1. + * The input tree array may contain duplicate trees. + * The sort order is written to the given output stream. + */ + protected ElementTree[] sortTrees(ElementTree[] trees, DataOutput output) throws IOException { + + /* the sorted list */ + int numTrees = trees.length; + ElementTree[] sorted = new ElementTree[numTrees]; + int[] order = new int[numTrees]; + + /* first build a table of ElementTree -> Vector of Integers(indices in trees array) */ + HashMap> table = new HashMap<>(numTrees * 2 + 1); + for (int i = 0; i < trees.length; i++) { + List indices = table.get(trees[i]); + if (indices == null) { + indices = new ArrayList<>(); + table.put(trees[i], indices); + } + indices.add(i); + } + + /* find the oldest tree (a descendent of all other trees) */ + ElementTree oldest = trees[ElementTree.findOldest(trees)]; + + /** + * Walk through the chain of trees from oldest to newest, + * adding them to the sorted list as we go. + */ + int i = numTrees - 1; + while (i >= 0) { + /* add all instances of the current oldest tree to the sorted list */ + List indices = table.remove(oldest); + for (Enumeration e = Collections.enumeration(indices); e.hasMoreElements();) { + Integer next = e.nextElement(); + sorted[i] = oldest; + order[i] = next.intValue(); + i--; + } + if (i >= 0) { + /* find the next tree in the list */ + ElementTree parent = oldest.getParent(); + while (table.get(parent) == null) { + parent = parent.getParent(); + } + oldest = parent; + } + } + + /* write the order array */ + for (i = 0; i < numTrees; i++) { + writeNumber(order[i], output); + } + return sorted; + } + + /** + * Writes the delta describing the changes that have to be made + * to newerTree to obtain olderTree. + * + * @param path The path of the subtree to write. All nodes on the path above + * the subtree are represented as empty nodes. + * @param depth The depth of the subtree to write. A depth of zero writes a + * single node, and a depth of D_INFINITE writes the whole subtree. + * @param output The stream to write the subtree to. + */ + public void writeDelta(ElementTree olderTree, ElementTree newerTree, IPath path, int depth, final DataOutput output, IElementComparator comparator) throws IOException { + + /* write the version number */ + writeNumber(CURRENT_FORMAT, output); + + /** + * Note that in current ElementTree usage, the newest + * tree is the complete tree, and older trees are just + * deltas on the new tree. + */ + DeltaDataTree completeTree = newerTree.getDataTree(); + DeltaDataTree derivedTree = olderTree.getDataTree(); + DeltaDataTree deltaToWrite = null; + + deltaToWrite = completeTree.forwardDeltaWith(derivedTree, comparator); + + Assert.isTrue(deltaToWrite.isImmutable()); + dataTreeWriter.writeTree(deltaToWrite, path, depth, output); + } + + /** + * Writes an array of ElementTrees to the given output stream. + * @param trees A chain of ElementTrees, where on tree in the list is + * complete, and all other trees are deltas on the previous tree in the list. + * @param path The path of the subtree to write. All nodes on the path above + * the subtree are represented as empty nodes. + * @param depth The depth of the subtree to write. A depth of zero writes a + * single node, and a depth of D_INFINITE writes the whole subtree. + * @param output The stream to write the subtree to. + + */ + public void writeDeltaChain(ElementTree[] trees, IPath path, int depth, DataOutput output, IElementComparator comparator) throws IOException { + /* Write the format version number */ + writeNumber(CURRENT_FORMAT, output); + + /* Write the number of trees */ + int treeCount = trees.length; + writeNumber(treeCount, output); + + if (treeCount <= 0) { + return; + } + + /** + * Sort the trees in ancestral order, + * which writes the tree order to the output + */ + ElementTree[] sortedTrees = sortTrees(trees, output); + + /* Write the complete tree */ + writeTree(sortedTrees[0], path, depth, output); + + /* Write the deltas for each of the remaining trees */ + for (int i = 1; i < treeCount; i++) { + writeDelta(sortedTrees[i], sortedTrees[i - 1], path, depth, output, comparator); + } + } + + /** + * 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, DataOutput output) throws IOException { + if (number >= 0 && number < 0xff) { + output.writeByte(number); + } else { + output.writeByte(0xff); + output.writeInt(number); + } + } + + /** + * Writes all or some of an element tree to an output stream. + * This always writes the most current version of the element tree + * file format, whereas the reader supports multiple versions. + * + * @param tree The tree to write + * @param path The path of the subtree to write. All nodes on the path above + * the subtree are represented as empty nodes. + * @param depth The depth of the subtree to write. A depth of zero writes a + * single node, and a depth of D_INFINITE writes the whole subtree. + * @param output The stream to write the subtree to. + */ + public void writeTree(ElementTree tree, IPath path, int depth, final DataOutput output) throws IOException { + + /* Write the format version number. */ + writeNumber(CURRENT_FORMAT, output); + + /* This actually just copies the root node, which is what we want */ + DeltaDataTree subtree = new DeltaDataTree(tree.getDataTree().copyCompleteSubtree(Path.ROOT)); + + dataTreeWriter.writeTree(subtree, path, depth, output); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java new file mode 100644 index 0000000000..b3bef46bd0 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * 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.watson; + +import org.eclipse.core.internal.dtree.IComparator; + +/** + * This interface allows clients of the element tree to specify + * how element infos are compared, and thus how element tree deltas + * are created. + */ +public interface IElementComparator extends IComparator { + /** + * The kinds of changes + */ + public int K_NO_CHANGE = 0; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.java new file mode 100644 index 0000000000..4b14f2f53a --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.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.watson; + +/** + * An interface for objects which can visit an element of + * an element tree and access that element's node info. + * @see ElementTreeIterator + */ +public interface IElementContentVisitor { + /** Visits a node (element). + *

          Note that elementContents is equal totree. + * getElement(elementPath) but takes no time. + * @param tree the element tree being visited + * @param elementContents the object at the node being visited on this call + * @param requestor callback object for requesting the path of the object being + * visited. + * @return true if this element's children should be visited, and false + * otherwise. + */ + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java new file mode 100644 index 0000000000..dd4f03b34a --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.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.watson; + +import java.io.*; +import org.eclipse.core.runtime.IPath; + +/** + * The IElementInfoFlattener interface supports + * reading and writing element info objects. + */ +public interface IElementInfoFlattener { + /** + * Reads an element info from the given input stream. + * @param elementPath 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 elementPath, + * which may be null. + */ + public Object readElement(IPath elementPath, DataInput input) throws IOException; + + /** + * Writes the given element to the output stream. + *

          N.B. The bytes written must be sufficient for the + * purposes of reading the object back in. + * @param elementPath the element's path in the tree + * @param element the object associated with the given path, + * which may be null. + */ + public void writeElement(IPath elementPath, Object element, DataOutput output) throws IOException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java new file mode 100644 index 0000000000..299f95661c --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java @@ -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 + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +/** + * User data that can be attached to the element tree itself. + */ +public interface IElementTreeData extends Cloneable { + /** + * ElementTreeData must define a publicly accessible clone method. + * This method can simply invoke Object's clone method. + */ + public Object clone(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.java new file mode 100644 index 0000000000..0172182241 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.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.watson; + +import org.eclipse.core.runtime.IPath; + +/** + * Callback interface so visitors can request the path of the object they + * are visiting. This avoids creating paths when they are not needed. + */ +public interface IPathRequestor { + public IPath requestPath(); + + public String requestName(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java new file mode 100644 index 0000000000..706bfa6ae7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2009, 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; + +/** + * A description of a file info matcher. + * @since 3.6 + */ +public final class FileInfoMatcherDescription { + + private String id; + + private Object arguments; + + public FileInfoMatcherDescription(String id, Object arguments) { + super(); + this.id = id; + this.arguments = arguments; + } + + public Object getArguments() { + return arguments; + } + + public String getId() { + return id; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((arguments == null) ? 0 : arguments.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + FileInfoMatcherDescription other = (FileInfoMatcherDescription) obj; + if (arguments == null) { + if (other.arguments != null) + return false; + } else if (!arguments.equals(other.arguments)) + return false; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildConfiguration.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildConfiguration.java new file mode 100644 index 0000000000..d35511a92d --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildConfiguration.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 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.resources; + +import org.eclipse.core.runtime.IAdaptable; + +/** + * Build Configurations provide a mechanism for orthogonal configuration specific + * builds within a single project. The resources plugin maintains build deltas per + * interested builder, per configuration, and allow build configurations to reference + * each other. + *

          + * All projects have at least one build configuration. By default this + * has name {@link #DEFAULT_CONFIG_NAME}. One configuration in the project is defined + * to be 'active'. The active configuration is built by default. If unset, the + * active configuration defaults to the first configuration in the project. + *

          + *

          + * Build configurations are created and set on the project description using: + * {@link IProjectDescription#setBuildConfigs(String[])}. + * Build configurations set on Projects must have unique non-null names. + *

          + *

          + * When a project is built, a specific configuration is built. This configuration + * is passed to the builders so they can adapt their behavior + * appropriately. Builders which don't care about configurations may ignore this, + * and work as before. + *

          + *

          + * Build configuration can reference other builds configurations. These references are created + * using {@link IWorkspace#newBuildConfig(String, String)}, and set on the referencing project + * with {@link IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[])}. + * A referenced build configuration may have a null configuration name which is resolved to the + * referenced project's current active build configuration at build time. + *

          + *

          + * Workspace build will ensure that the projects are built in an appropriate order as defined + * by the reference graph. + *

          + * + * @see IWorkspace#newBuildConfig(String, String) + * @see IProjectDescription#setActiveBuildConfig(String) + * @see IProjectDescription#setBuildConfigs(String[]) + * @see IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[]) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * @since 3.7 + */ +public interface IBuildConfiguration extends IAdaptable { + + /** + * The Id of the default build configuration + */ + public static final String DEFAULT_CONFIG_NAME = ""; //$NON-NLS-1$ + + /** + * @return the project that the config is for; never null. + */ + public IProject getProject(); + + /** + * Returns the human readable name of this build configuration. If this + * {@link IBuildConfiguration} is set on a Project, this can never be null. + *

          + * If this IBuildConfiguration is being used as a reference to a build configuration + * in another project, this may be null. Such build configuration references are + * resolved to the current active configuration at build time. + *

          + * @return the name of the configuration; or null if this is a reference to the active + * configuration + */ + public String getName(); + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java new file mode 100644 index 0000000000..a9836327a4 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 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.resources; + +/** + * Stores information about the context in which a builder was called. + * + *

          + * This can be interrogated by a builder to determine what's been built + * before, and what's being built after it, for this particular build + * invocation. + *

          + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * + * @since 3.7 + */ +public interface IBuildContext { + + /** + * Gets a array of build configurations that were built before this build configuration, + * as part of the current top-level build invocation. + * + * @return an array of all referenced build configurations that have been built + * in the current build; never null. + */ + public IBuildConfiguration[] getAllReferencedBuildConfigs(); + + /** + * Gets a array of build configurations that will be built after this build configuration, + * as part of the current top-level build invocation. + *

          + * If the array is empty, this configuration is the last in the build chain. + *

          + * + * @return an array of all referencing build configurations that will be built + * in the current build; never null. + */ + public IBuildConfiguration[] getAllReferencingBuildConfigs(); + + /** + * Returns the full array of configurations that were requested to be built + * by the API user. These configurations may be anywhere in the build + * order (depending on how the build graph has been flattened). + *

          + * This array won't include any build configurations being built by virtue + * of being referenced from a requested build configuration. + *

          + * May return the empty array if this is a top-level workspace build. + * + * @return an array of configurations that were requested to be built. + */ + public IBuildConfiguration[] getRequestedConfigs(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java new file mode 100644 index 0000000000..27670d1ca2 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Map; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A builder command names a builder and supplies a table of + * name-value argument pairs. + *

          + * Changes to a command will only take effect if the modified command is installed + * into a project description via {@link IProjectDescription#setBuildSpec(ICommand[])}. + *

          + * + * @see IProjectDescription + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ICommand { + + /** + * Returns a table of the arguments for this command, or null + * if there are no arguments. The argument names and values are both strings. + * + * @return a table of command arguments (key type : String + * value type : String), or null + * @see #setArguments(Map) + */ + public Map getArguments(); + + /** + * Returns the name of the builder to run for this command, or + * null if the name has not been set. + * + * @return the name of the builder, or null if not set + * @see #setBuilderName(String) + */ + public String getBuilderName(); + + /** + * Returns whether this build command responds to the given kind of build. + *

          + * By default, build commands respond to all kinds of builds. + *

          + * + * @param kind One of the *_BUILD constants defined + * on IncrementalProjectBuilder + * @return true if this build command responds to the specified + * kind of build, and false otherwise. + * @see #setBuilding(int, boolean) + * @since 3.1 + */ + public boolean isBuilding(int kind); + + /** + * Returns whether this command allows configuring of what kinds of builds + * it responds to. By default, commands are only configurable + * if the corresponding builder defines the {@link #isConfigurable} + * attribute in its builder extension declaration. A command that is not + * configurable will always respond to all kinds of builds. + * + * @return true If this command allows configuration of + * what kinds of builds it responds to, and false otherwise. + * @see #setBuilding(int, boolean) + * @since 3.1 + */ + public boolean isConfigurable(); + + /** + * Sets this command's arguments to be the given table of name-values + * pairs, or to null if there are no arguments. The argument + * names and values are both strings. + *

          + * Individual builders specify their argument expectations. + *

          + *

          + * Note that modifications to the arguments of a command + * being used in a running builder may affect the run of that builder + * but will not affect any subsequent runs. To change a command + * permanently you must install the command into the relevant project + * build spec using {@link IProjectDescription#setBuildSpec(ICommand[])}. + *

          + * + * @param args a table of command arguments (keys and values must + * both be of type String), or null + * @see #getArguments() + */ + public void setArguments(Map args); + + /** + * Sets the name of the builder to run for this command. + *

          + * The builder name comes from the extension that plugs in + * to the standard org.eclipse.core.resources.builders + * extension point. + *

          + * + * @param builderName the name of the builder + * @see #getBuilderName() + */ + public void setBuilderName(String builderName); + + /** + * Specifies whether this build command responds to the provided kind of build. + *

          + * When a command is configured to not respond to a given kind of build, the + * builder instance will not be called when a build of that kind is initiated. + *

          + * This method has no effect if this build command does not allow its + * build kinds to be configured. + *

          + * Note: + *

            + *
          • A request for INCREMENTAL_BUILD or AUTO_BUILD will result in the builder being called with the FULL_BUILD + * kind, if there is no previous delta (e.g. after a clean build). + *
          • + * If INCREMENTAL_BUILD (or AUTO_BUILD) is promoted to FULL_BUILD, the builder will be called, + * if the command responds to INCREMENTAL_BUILD (or AUTO_BUILD). + *
          • + * If INCREMENTAL_BUILD is promoted to FULL_BUILD, the builder will be called, + * if the command responds to FULL_BUILD. + *
          • + * If AUTO_BUILD is promoted to FULL_BUILD, the builder will be called, + * only if the command responds to AUTO_BUILD. + *
          • + *
          + * + * @param kind One of the *_BUILD constants defined + * on IncrementalProjectBuilder + * @param value true if this build command responds to the + * specified kind of build, and false otherwise. + * @see #isBuilding(int) + * @see #isConfigurable() + * @see IWorkspace#build(int, IProgressMonitor) + * @see IProject#build(int, IProgressMonitor) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#AUTO_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @since 3.1 + */ + public void setBuilding(int kind, boolean value); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java new file mode 100644 index 0000000000..be92d364b5 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java @@ -0,0 +1,562 @@ +/******************************************************************************* + * 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 + * Serge Beauchamp (Freescale Semiconductor) - [252996] add addFilter/removeFilter/getFilters + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.runtime.*; + +/** + * Interface for resources which may contain + * other resources (termed its members). While the + * workspace itself is not considered a container in this sense, the + * workspace root resource is a container. + *

          + * Containers implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

          + * + * @see Platform#getAdapterManager() + * @see IProject + * @see IFolder + * @see IWorkspaceRoot + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IContainer extends IResource, IAdaptable { + + /*==================================================================== + * Constants defining which members are wanted: + *====================================================================*/ + + /** + * Member constant (bit mask value 1) indicating that phantom member resources are + * to be included. + * + * @see IResource#isPhantom() + * @since 2.0 + */ + public static final int INCLUDE_PHANTOMS = 1; + + /** + * Member constant (bit mask value 2) indicating that team private members are + * to be included. + * + * @see IResource#isTeamPrivateMember() + * @since 2.0 + */ + public static final int INCLUDE_TEAM_PRIVATE_MEMBERS = 2; + + /** + * Member constant (bit mask value 4) indicating that derived resources + * are to be excluded. + * + * @see IResource#isDerived() + * @since 3.1 + */ + public static final int EXCLUDE_DERIVED = 4; + + /** + * Member constant (bit mask value 8) indicating that hidden resources + * are to be included. + * + * @see IResource#isHidden() + * @since 3.4 + */ + public static final int INCLUDE_HIDDEN = 8; + + /** + * Member constant (bit mask value 16) indicating that a resource + * should not be checked for existence. + * + * @see IResource#accept(IResourceProxyVisitor, int) + * @see IResource#accept(IResourceVisitor, int, int) + * @since 3.8 + */ + public static final int DO_NOT_CHECK_EXISTENCE = 16; + + /** + * Returns whether a resource of some type with the given path + * exists relative to this resource. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators are ignored. + * If the path is empty this container is checked for existence. + * + * @param path the path of the resource + * @return true if a resource of some type with the given path + * exists relative to this resource, and false otherwise + * @see IResource#exists() + */ + public boolean exists(IPath path); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

          + * Note that no attempt is made to exclude team-private member resources + * as with members. + *

          + * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource existing at the calculated path in the workspace. + *

          + * Note that path contains a relative path to the resource + * and all path special characters will be interpreted. Passing an empty string + * will result in returning this {@link IContainer} itself. + *

          + * + * @param path the relative path to the member resource, must be a valid path or path segment + * @return the member resource, or null if no such + * resource exists + * @see #members() + * @see IPath#isValidPath(String) + * @see IPath#isValidSegment(String) + */ + public IResource findMember(String path); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

          + * If the includePhantoms argument is false, + * only a member resource with the given path that exists will be returned. + * If the includePhantoms argument is true, + * the method also returns a resource if the workspace is keeping track of a + * phantom with that path. + *

          + * Note that no attempt is made to exclude team-private member resources + * as with members. + *

          + * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource (or phantom) existing at the calculated path in the workspace. + *

          + * Note that path contains a relative path to the resource + * and all path special characters will be interpreted. Passing an empty string + * will result in returning this {@link IContainer} itself. + *

          + * + * @param path the relative path to the member resource, must be a valid path or path segment + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @return the member resource, or null if no such + * resource exists + * @see #members(boolean) + * @see IResource#isPhantom() + * @see IPath#isValidPath(String) + * @see IPath#isValidSegment(String) + */ + public IResource findMember(String path, boolean includePhantoms); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

          + * Note that no attempt is made to exclude team-private member resources + * as with members. + *

          + * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource existing at the calculated path in the workspace. + *

          + * + * @param path the path of the desired resource + * @return the member resource, or null if no such + * resource exists + */ + public IResource findMember(IPath path); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

          + * If the includePhantoms argument is false, + * only a member resource with the given path that exists will be returned. + * If the includePhantoms argument is true, + * the method also returns a resource if the workspace is keeping track of a + * phantom with that path. + *

          + * Note that no attempt is made to exclude team-private member resources + * as with members. + *

          + * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource (or phantom) existing at the calculated path in the workspace. + *

          + * + * @param path the path of the desired resource + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @return the member resource, or null if no such + * resource exists + * @see #members(boolean) + * @see IResource#isPhantom() + */ + public IResource findMember(IPath path, boolean includePhantoms); + + /** + * Returns the default charset for resources in this container. + *

          + * This is a convenience method, fully equivalent to: + *

          +	 *   getDefaultCharset(true);
          +	 * 
          + *

          + * Note that this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

          + * + * @return the name of the default charset encoding for this container + * @exception CoreException if this method fails + * @see IContainer#getDefaultCharset(boolean) + * @see IFile#getCharset() + * @since 3.0 + */ + public String getDefaultCharset() throws CoreException; + + /** + * Returns the default charset for resources in this container. + *

          + * If checkImplicit is false, this method + * will return the charset defined by calling #setDefaultCharset, provided this + * container exists, or null otherwise. + *

          + * If checkImplicit is true, this method uses the following + * algorithm to determine the charset to be returned: + *

            + *
          1. the one explicitly set by calling #setDefaultCharset + * (with a non-null argument) on this container, if any, and this container + * exists, or
          2. + *
          3. the parent's default charset, if this container has a parent (is not the + * workspace root), or
          4. + *
          5. the charset returned by ResourcesPlugin#getEncoding.
          6. + *
          + *

          + * Note that this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

          + * @return the name of the default charset encoding for this container, + * or null + * @exception CoreException if this method fails + * @see IFile#getCharset() + * @since 3.0 + */ + public String getDefaultCharset(boolean checkImplicit) throws CoreException; + + /** + * Returns a handle to the file identified by the given path in this + * container. + *

          + * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

          + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource and is appended + * to this container's full path to form the full path of the resultant resource. + * A trailing separator is ignored. The path of the resulting resource must + * have at least two segments. + *

          + * + * @param path the path of the member file + * @return the (handle of the) member file + * @see #getFolder(IPath) + */ + public IFile getFile(IPath path); + + /** + * Returns a handle to the folder identified by the given path in this + * container. + *

          + * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

          + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource and is appended + * to this container's full path to form the full path of the resultant resource. + * A trailing separator is ignored. The path of the resulting resource must + * have at least two segments. + *

          + * + * @param path the path of the member folder + * @return the (handle of the) member folder + * @see #getFile(IPath) + */ + public IFolder getFolder(IPath path); + + /** + * Returns a list of existing member resources (projects, folders and files) + * in this resource, in no particular order. + *

          + * This is a convenience method, fully equivalent to members(IResource.NONE). + * Team-private member resources are not included in the result. + *

          + * Note that the members of a project or folder are the files and folders + * immediately contained within it. The members of the workspace root + * are the projects in the workspace. + *

          + * + * @return an array of members of this resource + * @exception CoreException if this request fails. Reasons include: + *
            + *
          • This resource does not exist.
          • + *
          • This resource is a project that is not open.
          • + *
          + * @see #findMember(IPath) + * @see IResource#isAccessible() + */ + public IResource[] members() throws CoreException; + + /** + * Returns a list of all member resources (projects, folders and files) + * in this resource, in no particular order. + *

          + * This is a convenience method, fully equivalent to: + *

          +	 *   members(includePhantoms ? INCLUDE_PHANTOMS : IResource.NONE);
          +	 * 
          + * Team-private member resources are not included in the result. + *

          + * + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @return an array of members of this resource + * @exception CoreException if this request fails. Reasons include: + *
            + *
          • This resource does not exist.
          • + *
          • includePhantoms is false and + * this resource does not exist.
          • + *
          • includePhantoms is false and + * this resource is a project that is not open.
          • + *
          + * @see #members(int) + * @see IResource#exists() + * @see IResource#isPhantom() + */ + public IResource[] members(boolean includePhantoms) throws CoreException; + + /** + * Returns a list of all member resources (projects, folders and files) + * in this resource, in no particular order. + *

          + * If the INCLUDE_PHANTOMS flag is not specified in the member + * flags (recommended), only member resources that exist will be returned. + * If the INCLUDE_PHANTOMS flag is specified, + * the result will also include any phantom member resources the + * workspace is keeping track of. + *

          + * If the INCLUDE_TEAM_PRIVATE_MEMBERS flag is specified + * in the member flags, team private members will be included along with + * the others. If the INCLUDE_TEAM_PRIVATE_MEMBERS flag + * is not specified (recommended), the result will omit any team private + * member resources. + *

          + * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, hidden + * members will be included along with the others. If the {@link #INCLUDE_HIDDEN} flag + * is not specified (recommended), the result will omit any hidden + * member resources. + *

          + *

          + * If the EXCLUDE_DERIVED flag is not specified, derived + * resources are included. If the EXCLUDE_DERIVED flag is + * specified in the member flags, derived resources are not included. + *

          + * + * @param memberFlags bit-wise or of member flag constants + * ({@link #INCLUDE_PHANTOMS}, {@link #INCLUDE_TEAM_PRIVATE_MEMBERS}, + * {@link #INCLUDE_HIDDEN} and {@link #EXCLUDE_DERIVED}) indicating which members are of interest + * @return an array of members of this resource + * @exception CoreException if this request fails. Reasons include: + *
            + *
          • This resource does not exist.
          • + *
          • the INCLUDE_PHANTOMS flag is not specified and + * this resource does not exist.
          • + *
          • the INCLUDE_PHANTOMS flag is not specified and + * this resource is a project that is not open.
          • + *
          + * @see IResource#exists() + * @since 2.0 + */ + public IResource[] members(int memberFlags) throws CoreException; + + /** + * Returns a list of recently deleted files inside this container that + * have one or more saved states in the local history. The depth parameter + * determines how deep inside the container to look. This resource may or + * may not exist in the workspace. + *

          + * When applied to an existing project resource, this method returns recently + * deleted files with saved states in that project. Note that local history is + * maintained with each individual project, and gets discarded when a project + * is deleted from the workspace. If applied to a deleted project, this method + * returns the empty list. + *

          + * When applied to the workspace root resource (depth infinity), this method + * returns all recently deleted files with saved states in all existing projects. + *

          + * When applied to a folder (or project) resource (depth one), + * this method returns all recently deleted member files with saved states. + *

          + * When applied to a folder resource (depth zero), + * this method returns an empty list unless there was a recently deleted file + * with saved states at the same path as the folder. + *

          + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

          + * + * @param depth depth limit: one of DEPTH_ZERO, DEPTH_ONE + * or DEPTH_INFINITE + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @return an array of recently deleted files + * @exception CoreException if this method fails + * @see IFile#getHistory(IProgressMonitor) + * @since 2.0 + */ + public IFile[] findDeletedMembersWithHistory(int depth, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the default charset for this container. Passing a value of null + * will remove the default charset setting for this resource. + * + * @param charset a charset string, or null + * @exception CoreException if this method fails Reasons include: + *
            + *
          • This resource does not exist.
          • + *
          • An error happened while persisting this setting.
          • + *
          + * @see IContainer#getDefaultCharset() + * @since 3.0 + * @deprecated Replaced by {@link #setDefaultCharset(String, IProgressMonitor)} which + * is a workspace operation and reports changes in resource deltas. + */ + @Deprecated + public void setDefaultCharset(String charset) throws CoreException; + + /** + * Sets the default charset for this container. Passing a value of null + * will remove the default charset setting for this resource. + *

          + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the encoding of affected resources has been changed. + *

          + *

          + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

          + * + * @param charset a charset string, or null + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @exception CoreException if this method fails Reasons include: + *
            + *
          • This resource is not accessible.
          • + *
          • An error happened while persisting this setting.
          • + *
          • Resource changes are disallowed during certain types of resource change + * event notification. See {@link IResourceChangeEvent} for more details.
          • + *
          + * @see IContainer#getDefaultCharset() + * @see IResourceRuleFactory#charsetRule(IResource) + * @since 3.0 + */ + public void setDefaultCharset(String charset, IProgressMonitor monitor) throws CoreException; + + /** + * Adds a new filter to this container. Filters restrict the set of files and directories + * in the underlying file system that will be included as members of this container. + *

          + * The {@link IResource#BACKGROUND_REFRESH} update flag controls when + * changes to the resource hierarchy under this container resulting from the new + * filter take effect. If this flag is specified, the resource hierarchy is updated in a + * separate thread after this method returns. If the flag is not specified, any resource + * changes resulting from the new filter will occur before this method returns. + *

          + *

          + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include an indication of any + * resources that have been removed as a result of the new filter. + *

          + *

          + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

          + * + * @param type ({@link IResourceFilterDescription#INCLUDE_ONLY} or + * {@link IResourceFilterDescription#EXCLUDE_ALL} and/or {@link IResourceFilterDescription#INHERITABLE}) + * @param matcherDescription the description of the matcher that will determine + * which {@link IFileInfo} instances will be excluded from the resource tree + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#BACKGROUND_REFRESH}) + * @param monitor a progress monitor, or null if progress reporting is not desired + * @return the description of the added filter + * @exception CoreException if this filter could not be added. Reasons include: + *
            + *
          • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
          • + *
          + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #getFilters() + * @see IResourceFilterDescription#delete(int, IProgressMonitor) + * + * @since 3.6 + */ + public IResourceFilterDescription createFilter(int type, FileInfoMatcherDescription matcherDescription, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Retrieve all filters on this container. + * If no filters exist for this resource, an empty array is returned. + * + * @return an array of filters + * @exception CoreException if this resource's filters could not be retrieved. Reasons include: + *
            + *
          • This resource is not a folder.
          • + * + * @see #createFilter(int, FileInfoMatcherDescription, int, IProgressMonitor) + * @see IResourceFilterDescription#delete(int, IProgressMonitor) + * @since 3.6 + */ + public IResourceFilterDescription[] getFilters() throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java new file mode 100644 index 0000000000..f9e7873567 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * A storage that knows how its contents are encoded. + * + *

            The IEncodedStorage interface extends IStorage + * in order to provide access to the charset to be used when decoding its + * contents. + *

            + * Clients may implement this interface. + *

            + * + * @since 3.0 + */ +public interface IEncodedStorage extends IStorage { + /** + * Returns the name of a charset encoding to be used when decoding this + * storage's contents into characters. Returns null if a proper + * encoding cannot be determined. + *

            + * Note that this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

            + * + * @return the name of a charset, or null + * @exception CoreException if an error happens while determining + * the charset. See any refinements for more information. + * @see IStorage#getContents() + */ + public String getCharset() throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java new file mode 100644 index 0000000000..9b968db6a4 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java @@ -0,0 +1,1162 @@ +/******************************************************************************* + * 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 + * Sergey Prigogin (Google) - [462440] IFile#getContents methods should specify the status codes for its exceptions + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.io.InputStream; +import java.io.Reader; +import java.net.URI; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentDescription; +import org.eclipse.core.runtime.content.IContentTypeManager; + +/** + * Files are leaf resources which contain data. + * The contents of a file resource is stored as a file in the local + * file system. + *

            + * Files, like folders, may exist in the workspace but + * not be local; non-local file resources serve as place-holders for + * files whose content and properties have not yet been fetched from + * a repository. + *

            + *

            + * Files implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

            + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFile extends IResource, IEncodedStorage, IAdaptable { + /** + * Character encoding constant (value 0) which identifies + * files that have an unknown character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UNKNOWN = 0; + /** + * Character encoding constant (value 1) which identifies + * files that are encoded with the US-ASCII character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_US_ASCII = 1; + /** + * Character encoding constant (value 2) which identifies + * files that are encoded with the ISO-8859-1 character encoding scheme, + * also known as ISO-LATIN-1. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_ISO_8859_1 = 2; + /** + * Character encoding constant (value 3) which identifies + * files that are encoded with the UTF-8 character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_8 = 3; + /** + * Character encoding constant (value 4) which identifies + * files that are encoded with the UTF-16BE character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_16BE = 4; + /** + * Character encoding constant (value 5) which identifies + * files that are encoded with the UTF-16LE character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_16LE = 5; + /** + * Character encoding constant (value 6) which identifies + * files that are encoded with the UTF-16 character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_16 = 6; + + /** + * Appends the entire contents of the given stream to this file. + *

            + * This is a convenience method, fully equivalent to: + *

            +	 *   appendContents(source, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
            +	 * 
            + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

            + *

            + * This method is long-running; progress and cancelation are provided + * by the given progress monitor. + *

            + * + * @param source an input stream containing the new contents of the file + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param keepHistory a flag indicating whether or not to store + * the current contents in the local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource does not exist.
            • + *
            • The corresponding location in the local file system + * is occupied by a directory.
            • + *
            • The workspace is not in sync with the corresponding location + * in the local file system and force is false.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            • The file modification validator disallowed the change.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #appendContents(java.io.InputStream,int,IProgressMonitor) + */ + public void appendContents(InputStream source, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Appends the entire contents of the given stream to this file. + * The stream, which must not be null, will get closed + * whether this method succeeds or fails. + *

            + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite a corresponding file in the local file system provided + * it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write a corresponding file in the local file system, overwriting any + * existing one if need be. In either case, if this method succeeds, the + * resource will be marked as being local (even if it wasn't before). + *

            + *

            + * If this file is non-local then this method will always fail. The only exception + * is when FORCE is specified and the file exists in the local + * file system. In this case the file is made local and the given contents are appended. + *

            + *

            + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of this file should be captured in the workspace's local + * history (properties are not recorded in the local history). The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying KEEP_HISTORY + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. This flag is ignored if the file was not previously local. + *

            + *

            + * Update flags other than FORCE and KEEP_HISTORY + * are ignored. + *

            + *

            + * Prior to modifying the contents of this file, the file modification validator (if provided + * by the VCM plug-in), will be given a chance to perform any last minute preparations. Validation + * is performed by calling IFileModificationValidator.validateSave on this file. + * If the validation fails, then this operation will fail. + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

            + *

            + * This method is long-running; progress and cancelation are provided + * by the given progress monitor. + *

            + * + * @param source an input stream containing the new contents of the file + * @param updateFlags bit-wise or of update flag constants + * (FORCE and KEEP_HISTORY) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource does not exist.
            • + *
            • The corresponding location in the local file system + * is occupied by a directory.
            • + *
            • The workspace is not in sync with the corresponding location + * in the local file system and FORCE is not specified.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            • The file modification validator disallowed the change.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void appendContents(InputStream source, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + *

            + * This is a convenience method, fully equivalent to: + *

            +	 *   create(source, (force ? FORCE : IResource.NONE), monitor);
            +	 * 
            + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param source an input stream containing the initial contents of the file, + * or null if the file should be marked as not local + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource already exists in the workspace.
            • + *
            • The parent of this resource does not exist.
            • + *
            • The parent of this resource is a virtual folder.
            • + *
            • The project of this resource is not accessible.
            • + *
            • The parent contains a resource of a different type + * at the same path as this resource.
            • + *
            • The name of this resource is not valid (according to + * IWorkspace.validateName).
            • + *
            • The corresponding location in the local file system is occupied + * by a directory.
            • + *
            • The corresponding location in the local file system is occupied + * by a file and force is false.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + */ + public void create(InputStream source, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + * The resource's contents are supplied by the data in the given stream. + * This method closes the stream whether it succeeds or fails. + * If the stream is null then a file is not created in the local + * file system and the created file resource is marked as being non-local. + *

            + * The {@link IResource#FORCE} update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If {@link IResource#FORCE} is not specified, the method will only attempt + * to write a file in the local file system if it does not already exist. + * This option ensures there is no unintended data loss; it is the recommended + * setting. However, if {@link IResource#FORCE} is specified, this method will + * attempt to write a corresponding file in the local file system, + * overwriting any existing one if need be. + *

            + *

            + * The {@link IResource#DERIVED} update flag indicates that this resource + * should immediately be set as a derived resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setDerived(boolean)} + * with a value of true immediately after creating the resource. + *

            + *

            + * The {@link IResource#TEAM_PRIVATE} update flag indicates that this resource + * should immediately be set as a team private resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setTeamPrivateMember(boolean)} + * with a value of true immediately after creating the resource. + *

            + *

            + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

            + *

            + * Update flags other than those listed above are ignored. + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param source an input stream containing the initial contents of the file, + * or null if the file should be marked as not local + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#FORCE}, {@link IResource#DERIVED}, and {@link IResource#TEAM_PRIVATE}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource already exists in the workspace.
            • + *
            • The parent of this resource does not exist.
            • + *
            • The parent of this resource is a virtual folder.
            • + *
            • The project of this resource is not accessible.
            • + *
            • The parent contains a resource of a different type + * at the same path as this resource.
            • + *
            • The name of this resource is not valid (according to + * IWorkspace.validateName).
            • + *
            • The corresponding location in the local file system is occupied + * by a directory.
            • + *
            • The corresponding location in the local file system is occupied + * by a file and FORCE is not specified.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#createRule(IResource) + * @since 2.0 + */ + public void create(InputStream source, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + * The file's contents will be located in the file specified by the given + * file system path. The given path must be either an absolute file system + * path, or a relative path whose first segment is the name of a workspace path + * variable. + *

            + * The {@link IResource#ALLOW_MISSING_LOCAL} update flag controls how this + * method deals with cases where the local file system file to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If {@link IResource#ALLOW_MISSING_LOCAL} is specified, the operation will succeed + * even if the local file is missing, or the path is relative to an undefined + * variable. If {@link IResource#ALLOW_MISSING_LOCAL} is not specified, the operation + * will fail in the case where the local file system file does not exist or the + * path is relative to an undefined variable. + *

            + *

            + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then the existing linked resource's location is replaced + * by localLocation's value. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

            + *

            + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

            + *

            + * Update flags other than those listed above are ignored. + *

            + *

            + * This method synchronizes this resource with the local file system at the given + * location. + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param localLocation a file system path where the file should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE} and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource already exists in the workspace.
            • + *
            • The workspace contains a resource of a different type + * at the same path as this resource.
            • + *
            • The parent of this resource does not exist.
            • + *
            • The parent of this resource is not an open project
            • + *
            • The name of this resource is not valid (according to + * IWorkspace.validateName).
            • + *
            • The corresponding location in the local file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
            • + *
            • The corresponding location in the local file system is occupied + * by a directory (as opposed to a file).
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            • The team provider for the project which contains this folder does not permit + * linked resources.
            • + *
            • This folder's project contains a nature which does not permit linked resources.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#HIDDEN + * @since 2.1 + */ + public void createLink(IPath localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + * The file's contents will be located in the file specified by the given + * URI. The given URI must be either absolute, or a relative URI whose first path + * segment is the name of a workspace path variable. + *

            + * The ALLOW_MISSING_LOCAL update flag controls how this + * method deals with cases where the file system file to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If ALLOW_MISSING_LOCAL is specified, the operation will succeed + * even if the local file is missing, or the path is relative to an undefined + * variable. If ALLOW_MISSING_LOCAL is not specified, the operation + * will fail in the case where the file system file does not exist or the + * path is relative to an undefined variable. + *

            + *

            + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then any existing resource with the same name is removed + * from the workspace to make way for creation of the link. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

            + *

            + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

            + *

            + * Update flags other than those listed above are ignored. + *

            + *

            + * This method synchronizes this resource with the file system at the given + * location. + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param location a file system URI where the file should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE} and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource already exists in the workspace.
            • + *
            • The workspace contains a resource of a different type + * at the same path as this resource.
            • + *
            • The parent of this resource does not exist.
            • + *
            • The parent of this resource is not an open project
            • + *
            • The name of this resource is not valid (according to + * IWorkspace.validateName).
            • + *
            • The corresponding location in the file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
            • + *
            • The corresponding location in the file system is occupied + * by a directory (as opposed to a file).
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            • The team provider for the project which contains this folder does not permit + * linked resources.
            • + *
            • This folder's project contains a nature which does not permit linked resources.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#HIDDEN + * @since 3.2 + */ + public void createLink(URI location, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this file from the workspace. + *

            + * This is a convenience method, fully equivalent to: + *

            +	 *   delete((keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
            +	 * 
            + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this folder has been removed from its parent. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource could not be deleted for some reason.
            • + *
            • This resource is out of sync with the local file system + * and force is false.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + * @see IResourceRuleFactory#deleteRule(IResource) + */ + public void delete(boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the name of a charset to be used when decoding the contents of this + * file into characters. + *

            + * This refinement of the corresponding {@link IEncodedStorage} method + * is a convenience method, fully equivalent to: + *

            +	 *   getCharset(true);
            +	 * 
            + *

            + * Note 1: this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

            + *

            + * Note 2: this method returns a cached value for the encoding + * that may be out of date if the file is not synchronized with the local file system + * and the encoding has since changed in the file system. + *

            + * + * @return the name of a charset + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource could not be read.
            • + *
            • This resource is not local.
            • + *
            • The corresponding location in the local file system + * is occupied by a directory.
            • + *
            + * @see IFile#getCharset(boolean) + * @see IEncodedStorage#getCharset() + * @see IContainer#getDefaultCharset() + * @since 3.0 + */ + @Override + public String getCharset() throws CoreException; + + /** + * Returns the name of a charset to be used when decoding the contents of this + * file into characters. + *

            + * If checkImplicit is false, this method will return the + * charset defined by calling setCharset, provided this file + * exists, or null otherwise. + *

            + * If checkImplicit is true, this method uses the following + * algorithm to determine the charset to be returned: + *

              + *
            1. the charset defined by calling #setCharset, if any, and this file + * exists, or
            2. + *
            3. the charset automatically discovered based on this file's contents, + * if one can be determined, or
            4. + *
            5. the default encoding for this file's parent (as defined by + * IContainer#getDefaultCharset).
            6. + *
            + *

            + * Note 1: this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

            + *

            + * Note 2: this method returns a cached value for the encoding + * that may be out of date if the file is not synchronized with the local file system + * and the encoding has since changed in the file system. + *

            + * + * @return the name of a charset, or null + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource could not be read.
            • + *
            • This resource is not local.
            • + *
            • The corresponding location in the local file system + * is occupied by a directory.
            • + *
            + * @see IEncodedStorage#getCharset() + * @see IContainer#getDefaultCharset() + * @since 3.0 + */ + public String getCharset(boolean checkImplicit) throws CoreException; + + /** + * Returns the name of a charset to be used to encode the given contents + * when saving to this file. This file does not have to exist. The character stream is not automatically closed. + *

            + * This method uses the following algorithm to determine the charset to be returned: + *

              + *
            1. if this file exists, the charset returned by IFile#getCharset(false), if one is defined, or
            2. + *
            3. the charset automatically discovered based on the file name and the given contents, + * if one can be determined, or
            4. + *
            5. the default encoding for the parent resource (as defined by + * IContainer#getDefaultCharset).
            6. + *
            + *

            + * Note: this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

            + * + * @param reader a character stream containing the contents to be saved into this file + * @return the name of a charset + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • The given character stream could not be read.
            • + *
            + * @see #getCharset(boolean) + * @see IContainer#getDefaultCharset() + * @since 3.1 + */ + public String getCharsetFor(Reader reader) throws CoreException; + + /** + * Returns a description for this file's current contents. Returns + * null if a description cannot be obtained. + *

            + * Calling this method produces a similar effect as calling + * getDescriptionFor(getContents(), getName(), IContentDescription.ALL) + * on IContentTypeManager, but provides better + * opportunities for improved performance. Therefore, when manipulating + * IFiles, clients should call this method instead of + * IContentTypeManager.getDescriptionFor. + *

            + * + * @return a description for this file's current contents, or + * null + * @exception CoreException if this method fails. The status code associated with exception + * reflects the cause of the failure. Reasons include: + *
              + *
            • {@link IResourceStatus#RESOURCE_NOT_FOUND} - This file does not exist. + * Please notice that a successful {@link #exists()} check prior to calling + * {@link #getContentDescription()} does not guarantee the file existence since the file + * may be deleted outside Eclipse at the very last moment.
            • + *
            • {@link IResourceStatus#RESOURCE_NOT_LOCAL} - This resource is not local.
            • + *
            • {@link IResourceStatus#OUT_OF_SYNC_LOCAL} - The workspace is not in sync with + * the corresponding location in the local file system (and + * {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is disabled).
            • + *
            • {@link IResourceStatus#FAILED_DESCRIBING_CONTENTS} - An I/O error occurred while + * reading the file.
            • + *
            + * @see IContentDescription + * @see IContentTypeManager#getDescriptionFor(InputStream, String, QualifiedName[]) + * @since 3.0 + */ + public IContentDescription getContentDescription() throws CoreException; + + /** + * Returns an open input stream on the contents of this file. + *

            + * This refinement of the corresponding {@link IStorage} method + * is a convenience method returning an open input stream. It's equivalent to: + *

            +	 *   getContents(RefreshManager#PREF_LIGHTWEIGHT_AUTO_REFRESH);
            +	 * 
            + *

            + *

            + * If lightweight auto-refresh is not enabled this method will throw a CoreException + * when opening out-of-sync resources. + *

            + * The client is responsible for closing the stream when finished. + * + * @return an input stream containing the contents of the file + * @exception CoreException if this method fails. The status code associated with exception + * reflects the cause of the failure. Reasons include: + *
              + *
            • {@link IResourceStatus#RESOURCE_NOT_FOUND} - This file does not exist. + * Please notice that a successful {@link #exists()} check prior to calling + * {@link #getContents()} does not guarantee the file existence since the file may be + * deleted outside Eclipse at the very last moment.
            • + *
            • {@link IResourceStatus#RESOURCE_NOT_LOCAL} - This resource is not local.
            • + *
            • {@link IResourceStatus#RESOURCE_WRONG_TYPE} - The file-system resource is not + * a file.
            • + *
            • {@link IResourceStatus#OUT_OF_SYNC_LOCAL} - The workspace is not in sync with + * the corresponding location in the local file system (and + * {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is disabled).
            • + *
            + */ + @Override + public InputStream getContents() throws CoreException; + + /** + * This refinement of the corresponding IStorage method + * returns an open input stream on the contents of this file. + * The client is responsible for closing the stream when finished. + * If force is true the file is opened and an input + * stream returned regardless of the sync state of the file. The file + * is not synchronized with the workspace. + * If force is false the method fails if not in sync. + * + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @return an input stream containing the contents of the file + * @exception CoreException if this method fails. The status code associated with exception + * reflects the cause of the failure. Reasons include: + *
              + *
            • {@link IResourceStatus#RESOURCE_NOT_FOUND} - This file does not exist. + * Please notice that a successful {@link #exists()} check prior to calling + * {@link #getContents()} does not guarantee the file existence since the file may be + * deleted outside Eclipse at the very last moment.
            • + *
            • {@link IResourceStatus#RESOURCE_NOT_LOCAL} - This resource is not local.
            • + *
            • {@link IResourceStatus#RESOURCE_WRONG_TYPE} - The file-system resource is not + * a file.
            • + *
            • {@link IResourceStatus#OUT_OF_SYNC_LOCAL} - The workspace is not in sync with + * the corresponding location in the local file system (and + * {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is disabled).
            • + *
            + */ + public InputStream getContents(boolean force) throws CoreException; + + /** + * Returns a constant identifying the character encoding of this file, or + * ENCODING_UNKNOWN if it could not be determined. The returned constant + * will be one of the ENCODING_* constants defined on IFile. + * + * This method attempts to guess the file's character encoding by analyzing + * the first few bytes of the file. If no identifying pattern is found at the + * beginning of the file, ENC_UNKNOWN will be returned. This method will + * not attempt any complex analysis of the file to make a guess at the + * encoding that is used. + * + * @return The character encoding of this file + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource does not exist.
            • + *
            • This resource could not be read.
            • + *
            • This resource is not local.
            • + *
            • The corresponding location in the local file system + * is occupied by a directory.
            • + *
            + * @deprecated use {@link #getCharset} instead + */ + @Deprecated + public int getEncoding() throws CoreException; + + /** + * Returns the full path of this file. + * This refinement of the corresponding IStorage and IResource + * methods links the semantics of resource and storage object paths such that + * IFiles always have a path and that path is relative to the + * containing workspace. + * + * @see IResource#getFullPath() + * @see IStorage#getFullPath() + */ + @Override + public IPath getFullPath(); + + /** + * Returns a list of past states of this file known to this workspace. + * Recently added states first. + *

            + * 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 + * @return an array of states of this file + * @exception CoreException if this method fails. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + */ + public IFileState[] getHistory(IProgressMonitor monitor) throws CoreException; + + /** + * Returns the name of this file. + * This refinement of the corresponding IStorage and IResource + * methods links the semantics of resource and storage object names such that + * IFiles always have a name and that name equivalent to the + * last segment of its full path. + * + * @see IResource#getName() + * @see IStorage#getName() + */ + @Override + public String getName(); + + /** + * Returns whether this file is read-only. + * This refinement of the corresponding IStorage and IResource + * methods links the semantics of read-only resources and read-only storage objects. + * + * @see IResource#isReadOnly() + * @see IStorage#isReadOnly() + */ + @SuppressWarnings("deprecation") + @Override + public boolean isReadOnly(); + + /** + * Moves this resource to be at the given location. + *

            + * This is a convenience method, fully equivalent to: + *

            +	 *   move(destination, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
            +	 * 
            + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file has been removed from its parent and a new file + * has been added to the parent of the destination. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
              + *
            • This resource does not exist.
            • + *
            • This resource is not local.
            • + *
            • The resource corresponding to the parent destination path does not exist.
            • + *
            • The resource corresponding to the parent destination path is a closed + * project.
            • + *
            • A resource at destination path does exist.
            • + *
            • A resource of a different type exists at the destination path.
            • + *
            • This resource is out of sync with the local file system + * and force is false.
            • + *
            • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IResource#move(IPath,int,IProgressMonitor) + * @see IResourceRuleFactory#moveRule(IResource, IResource) + */ + public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the charset for this file. Passing a value of null + * will remove the charset setting for this resource. + * + * @param newCharset a charset name, or null + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource does not exist.
            • + *
            • An error happened while persisting this setting.
            • + *
            + * @see #getCharset() + * @since 3.0 + * @deprecated Replaced by {@link #setCharset(String, IProgressMonitor)} which + * is a workspace operation and reports changes in resource deltas. + */ + @Deprecated + public void setCharset(String newCharset) throws CoreException; + + /** + * Sets the charset for this file. Passing a value of null + * will remove the charset setting for this resource. + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's encoding has changed. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param newCharset a charset name, or null + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource does not exist.
            • + *
            • An error happened while persisting this setting.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See {@link IResourceChangeEvent} for more details.
            • + *
            + * @see #getCharset() + * @see IResourceRuleFactory#charsetRule(IResource) + * @since 3.0 + */ + public void setCharset(String newCharset, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given input stream. + *

            + * This is a convenience method, fully equivalent to: + *

            +	 *   setContents(source, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
            +	 * 
            + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's contents have been changed. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param source an input stream containing the new contents of the file + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param keepHistory a flag indicating whether or not store + * the current contents in the local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource does not exist.
            • + *
            • The corresponding location in the local file system + * is occupied by a directory.
            • + *
            • The workspace is not in sync with the corresponding location + * in the local file system and force is false.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            • The file modification validator disallowed the change.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #setContents(java.io.InputStream,int,IProgressMonitor) + */ + public void setContents(InputStream source, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given file state. + *

            + * This is a convenience method, fully equivalent to: + *

            +	 *   setContents(source, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
            +	 * 
            + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param source a previous state of this resource + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param keepHistory a flag indicating whether or not store + * the current contents in the local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource does not exist.
            • + *
            • The state does not exist.
            • + *
            • The corresponding location in the local file system + * is occupied by a directory.
            • + *
            • The workspace is not in sync with the corresponding location + * in the local file system and force is false.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            • The file modification validator disallowed the change.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #setContents(IFileState,int,IProgressMonitor) + */ + public void setContents(IFileState source, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given input stream. + * The stream will get closed whether this method succeeds or fails. + * If the stream is null then the content is set to be the + * empty sequence of bytes. + *

            + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite a corresponding file in the local file system provided + * it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write a corresponding file in the local file system, overwriting any + * existing one if need be. In either case, if this method succeeds, the + * resource will be marked as being local (even if it wasn't before). + *

            + *

            + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of this file should be captured in the workspace's local + * history (properties are not recorded in the local history). The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying KEEP_HISTORY + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. This flag is ignored if the file was not previously local. + *

            + *

            + * Update flags other than FORCE and KEEP_HISTORY + * are ignored. + *

            + *

            + * Prior to modifying the contents of this file, the file modification validator (if provided + * by the VCM plug-in), will be given a chance to perform any last minute preparations. Validation + * is performed by calling IFileModificationValidator.validateSave on this file. + * If the validation fails, then this operation will fail. + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param source an input stream containing the new contents of the file + * @param updateFlags bit-wise or of update flag constants + * (FORCE and KEEP_HISTORY) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource does not exist.
            • + *
            • The corresponding location in the local file system + * is occupied by a directory.
            • + *
            • The workspace is not in sync with the corresponding location + * in the local file system and FORCE is not specified.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            • The file modification validator disallowed the change.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void setContents(InputStream source, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given file state. + *

            + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite a corresponding file in the local file system provided + * it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write a corresponding file in the local file system, overwriting any + * existing one if need be. In either case, if this method succeeds, the + * resource will be marked as being local (even if it wasn't before). + *

            + *

            + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of this file should be captured in the workspace's local + * history (properties are not recorded in the local history). The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying KEEP_HISTORY + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. This flag is ignored if the file was not previously local. + *

            + *

            + * Update flags other than FORCE and KEEP_HISTORY + * are ignored. + *

            + *

            + * Prior to modifying the contents of this file, the file modification validator (if provided + * by the VCM plug-in), will be given a chance to perform any last minute preparations. Validation + * is performed by calling IFileModificationValidator.validateSave on this file. + * If the validation fails, then this operation will fail. + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param source a previous state of this resource + * @param updateFlags bit-wise or of update flag constants + * (FORCE and KEEP_HISTORY) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource does not exist.
            • + *
            • The state does not exist.
            • + *
            • The corresponding location in the local file system + * is occupied by a directory.
            • + *
            • The workspace is not in sync with the corresponding location + * in the local file system and FORCE is not specified.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            • The file modification validator disallowed the change.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void setContents(IFileState source, int updateFlags, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java new file mode 100644 index 0000000000..7880447c2c --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * 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; + +import org.eclipse.core.resources.team.FileModificationValidator; +import org.eclipse.core.runtime.IStatus; + +/** + * The file modification validator is a Team-related hook for pre-checking operations + * that modify the contents of files. + *

            + * This interface is used only in conjunction with the + * "org.eclipse.core.resources.fileModificationValidator" + * extension point. It is intended to be implemented only + * by the Eclipse Platform Team plug-in. + *

            + * + * @since 2.0 + * @deprecated clients should subclass {@link FileModificationValidator} instead + * of implementing this interface + */ +@Deprecated +public interface IFileModificationValidator { + /** + * Validates that the given files can be modified. The files must all exist + * in the workspace. The optional context object may be supplied if + * UI-based validation is required. If the context is null, the + * validator must attempt to perform the validation in a headless manner. + * The returned status is IStatus.OK if this validator + * believes the given file can be modified. Other return statuses indicate + * the reason why the individual files cannot be modified. + * + * @param files the files that are to be modified; these files must all exist in the workspace + * @param context the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null if there is no UI context (declared + * as an Object to avoid any direct references on the SWT component) + * @return a status object that is OK if things are fine, otherwise a status describing + * reasons why modifying the given files is not reasonable + * @see IWorkspace#validateEdit(IFile[], Object) + */ + public IStatus validateEdit(IFile[] files, Object context); + + /** + * Validates that the given file can be saved. This method is called from + * IFile#setContents and IFile#appendContents + * before any attempt to write data to disk. The returned status is + * IStatus.OK if this validator believes the given file can be + * successfully saved. In all other cases the return value is a non-OK status. + * Note that a return value of IStatus.OK does not guarantee + * that the save will succeed. + * + * @param file the file that is to be modified; this file must exist in the workspace + * @return a status indicating whether or not it is reasonable to try writing to the given file; + * IStatus.OK indicates a save should be attempted. + * + * @see IFile#setContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + * @see IFile#appendContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus validateSave(IFile file); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java new file mode 100644 index 0000000000..bf354eaf49 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.resources; + +import java.io.InputStream; +import org.eclipse.core.runtime.*; + +/** + * A previous state of a file stored in the workspace's local history. + *

            + * Certain methods for updating, deleting, or moving a file cause the + * "before" contents of the file to be copied to an internal area of the + * workspace called the local history area thus providing + * a limited history of earlier states of a file. + *

            + *

            + * Moving or copying a file will cause a copy of its local history to appear + * at the new location as well as at the original location. Subsequent + * changes to either file will only affect the local history of the file + * changed. Deleting a file and creating another one at the + * same path does not affect the history. If the original file had + * history, that same history will be available for the new one. + *

            + *

            + * The local history does not track resource properties. + * File states are volatile; the platform does not guarantee that a + * certain state will always be in the local history. + *

            + *

            + * File state objects implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

            + * + * @see IFile + * @see IStorage + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFileState extends IEncodedStorage, IAdaptable { + /** + * Returns whether this file state still exists in the local history. + * + * @return true if this state exists, and false + * if it does not + */ + public boolean exists(); + + /** + * Returns an open input stream on the contents of this file state. + * This refinement of the corresponding + * IStorage method returns an open input stream + * on the contents this file state represents. + * The client is responsible for closing the stream when finished. + * + * @return an input stream containing the contents of the file + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This state does not exist.
            • + *
            + */ + @Override + public InputStream getContents() throws CoreException; + + /** + * Returns the full path of this file state. + * This refinement of the corresponding IStorage + * method specifies that IFileStates always have a + * path and that path is the full workspace path of the file represented by this state. + * + * @see IResource#getFullPath() + * @see IStorage#getFullPath() + */ + @Override + public IPath getFullPath(); + + /** + * Returns the modification time of the file. If you create a file at + * 9:00 and modify it at 11:00, the file state added to the history + * at 11:00 will have 9:00 as its modification time. + *

            + * Note that is used only to give the user a general idea of how + * old this file state is. + * + * @return the time of last modification, in milliseconds since + * January 1, 1970, 00:00:00 GMT. + */ + public long getModificationTime(); + + /** + * Returns the name of this file state. + * This refinement of the corresponding IStorage + * method specifies that IFileStates always have a + * name and that name is equivalent to the last segment of the full path + * of the resource represented by this state. + * + * @see IResource#getName() + * @see IStorage#getName() + */ + @Override + public String getName(); + + /** + * Returns whether this file state is read-only. + * This refinement of the corresponding + * IStorage method restricts IFileStates to + * always be read-only. + * + * @see IStorage + */ + @Override + public boolean isReadOnly(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java new file mode 100644 index 0000000000..2b288af079 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2009 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 + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.resources.filtermatchers.AbstractFileInfoMatcher; + +/** + * A filter descriptor contains information about a filter type + * obtained from the plug-in manifest (plugin.xml) files. + *

            + * Filter descriptors are platform-defined objects that exist + * independent of whether that filter's bundle has been started. + *

            + * + * @see AbstractFileInfoMatcher + * @see IWorkspace#getFilterMatcherDescriptor(String) + * @see IWorkspace#getFilterMatcherDescriptors() + * @since 3.6 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFilterMatcherDescriptor { + + /** + * An argument filter type constant (value "filter"), denoting that this + * filter takes another filter as argument. + */ + public static final String ARGUMENT_TYPE_FILTER_MATCHER = "filterMatcher"; //$NON-NLS-1$ + /** + * An argument filter type constant (value "filters"), denoting that this + * filter takes an array of other filters as argument. + */ + public static final String ARGUMENT_TYPE_FILTER_MATCHERS = "filterMatchers"; //$NON-NLS-1$ + /** + * An argument filter type constant (value "none"), denoting that this + * filter does not take any arguments. + */ + public static final String ARGUMENT_TYPE_NONE = "none"; //$NON-NLS-1$ + /** + * An argument filter type constant (value "string"), denoting that this + * filter takes a string argument + */ + public static final String ARGUMENT_TYPE_STRING = "string"; //$NON-NLS-1$ + + /** + * Returns the argument type expected by this filter. The result will be one of the + * ARGUMENT_TYPE_* constants declared on this class. + * @return The argument type of this filter extension + */ + public abstract String getArgumentType(); + + /** + * Returns a translated, human-readable description for this filter extension. + * @return The human-readable filter description + */ + public abstract String getDescription(); + + /** + * Returns the fully qualified id of the filter extension. + * @return The fully qualified id of the filter extension. + */ + public abstract String getId(); + + /** + * Returns a translated, human-readable name for this filter extension. + * @return The human-readable filter name + */ + public abstract String getName(); + + /** + * TODO What is this? + */ + public abstract boolean isFirstOrdering(); + +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java new file mode 100644 index 0000000000..162305ef73 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java @@ -0,0 +1,461 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import org.eclipse.core.runtime.*; + +/** + * Folders may be leaf or non-leaf resources and may contain files and/or other folders. + * A folder resource is stored as a directory in the local file system. + *

            + * Folders, like other resource types, may exist in the workspace but + * not be local; non-local folder resources serve as place-holders for + * folders whose properties have not yet been fetched from a repository. + *

            + *

            + * Folders implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

            + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFolder extends IContainer, IAdaptable { + + /** + * Creates a new folder resource as a member of this handle's parent resource. + *

            + * This is a convenience method, fully equivalent to: + *

            +	 *   create((force ? FORCE : IResource.NONE), local, monitor);
            +	 * 
            + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param local a flag controlling whether or not the folder will be local + * after the creation + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource already exists in the workspace.
            • + *
            • The workspace contains a resource of a different type + * at the same path as this resource.
            • + *
            • The parent of this resource does not exist.
            • + *
            • The parent of this resource is a project that is not open.
            • + *
            • The parent contains a resource of a different type + * at the same path as this resource.
            • + *
            • The parent of this resource is virtual, but this resource is not.
            • + *
            • The name of this resource is not valid (according to + * IWorkspace.validateName).
            • + *
            • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
            • + *
            • The corresponding location in the local file system is occupied + * by a folder and force is false.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IFolder#create(int,boolean,IProgressMonitor) + */ + public void create(boolean force, boolean local, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new folder resource as a member of this handle's parent resource. + *

            + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to create a directory in the local file system if there isn't one already. + * This option ensures there is no unintended data loss; it is the recommended + * setting. However, if FORCE is specified, this method will + * be deemed a success even if there already is a corresponding directory. + *

            + *

            + * The {@link IResource#DERIVED} update flag indicates that this resource + * should immediately be set as a derived resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setDerived(boolean)} + * with a value of true immediately after creating the resource. + *

            + *

            + * The {@link IResource#TEAM_PRIVATE} update flag indicates that this resource + * should immediately be set as a team private resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setTeamPrivateMember(boolean)} + * with a value of true immediately after creating the resource. + *

            + *

            + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

            + *

            + * Update flags other than those listed above are ignored. + *

            + *

            + * This method synchronizes this resource with the local file system. + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#FORCE}, {@link IResource#DERIVED}, {@link IResource#TEAM_PRIVATE}) + * and {@link IResource#VIRTUAL}) + * @param local a flag controlling whether or not the folder will be local + * after the creation + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource already exists in the workspace.
            • + *
            • The workspace contains a resource of a different type + * at the same path as this resource.
            • + *
            • The parent of this resource does not exist.
            • + *
            • The parent of this resource is a project that is not open.
            • + *
            • The parent contains a resource of a different type + * at the same path as this resource.
            • + *
            • The parent of this resource is virtual, but this resource is not.
            • + *
            • The name of this resource is not valid (according to + * IWorkspace.validateName).
            • + *
            • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
            • + *
            • The corresponding location in the local file system is occupied + * by a folder and FORCE is not specified.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#createRule(IResource) + * @since 2.0 + */ + public void create(int updateFlags, boolean local, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new folder resource as a member of this handle's parent resource. + * The folder's contents will be located in the directory specified by the given + * file system path. The given path must be either an absolute file system + * path, or a relative path whose first segment is the name of a workspace path + * variable. + *

            + * The ALLOW_MISSING_LOCAL update flag controls how this + * method deals with cases where the local file system directory to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If ALLOW_MISSING_LOCAL is specified, the operation will succeed + * even if the local directory is missing, or the path is relative to an + * undefined variable. If ALLOW_MISSING_LOCAL is not specified, the + * operation will fail in the case where the local file system directory does + * not exist or the path is relative to an undefined variable. + *

            + *

            + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then the existing linked resource's location is replaced + * by localLocation's value. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

            + *

            + * The {@link IResource#BACKGROUND_REFRESH} update flag controls how + * this method synchronizes the new resource with the filesystem. If this flag is + * specified, resources on disk will be synchronized in the background after the + * method returns. Child resources of the link may not be available until + * this background refresh completes. If this flag is not specified, resources are + * synchronized in the foreground before this method returns. + *

            + *

            + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

            + *

            + * Update flags other than those listed above are ignored. + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param localLocation a file system path where the folder should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE}, {@link IResource#BACKGROUND_REFRESH}, and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource already exists in the workspace.
            • + *
            • The workspace contains a resource of a different type + * at the same path as this resource.
            • + *
            • The parent of this resource does not exist.
            • + *
            • The parent of this resource is not an open project
            • + *
            • The name of this resource is not valid (according to + * IWorkspace.validateName).
            • + *
            • The corresponding location in the local file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
            • + *
            • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            • The team provider for the project which contains this folder does not permit + * linked resources.
            • + *
            • This folder's project contains a nature which does not permit linked resources.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#BACKGROUND_REFRESH + * @see IResource#HIDDEN + * @since 2.1 + */ + public void createLink(IPath localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new folder resource as a member of this handle's parent resource. + * The folder's contents will be located in the directory specified by the given + * file system URI. The given URI must be either absolute, or a relative URI + * whose first path segment is the name of a workspace path variable. + *

            + * The ALLOW_MISSING_LOCAL update flag controls how this + * method deals with cases where the local file system directory to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If ALLOW_MISSING_LOCAL is specified, the operation will succeed + * even if the local directory is missing, or the path is relative to an + * undefined variable. If ALLOW_MISSING_LOCAL is not specified, the + * operation will fail in the case where the local file system directory does + * not exist or the path is relative to an undefined variable. + *

            + *

            + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then any existing resource with the same name is removed + * from the workspace to make way for creation of the link. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

            + *

            + * The {@link IResource#BACKGROUND_REFRESH} update flag controls how + * this method synchronizes the new resource with the filesystem. If this flag is + * specified, resources on disk will be synchronized in the background after the + * method returns. Child resources of the link may not be available until + * this background refresh completes. If this flag is not specified, resources are + * synchronized in the foreground before this method returns. + *

            + *

            + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

            + *

            + * Update flags other than those listed above are ignored. + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param location a file system path where the folder should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE}, {@link IResource#BACKGROUND_REFRESH}, and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource already exists in the workspace.
            • + *
            • The workspace contains a resource of a different type + * at the same path as this resource.
            • + *
            • The parent of this resource does not exist.
            • + *
            • The parent of this resource is not an open project
            • + *
            • The name of this resource is not valid (according to + * IWorkspace.validateName).
            • + *
            • The corresponding location in the local file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
            • + *
            • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            • The team provider for the project which contains this folder does not permit + * linked resources.
            • + *
            • This folder's project contains a nature which does not permit linked resources.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#BACKGROUND_REFRESH + * @see IResource#HIDDEN + * @since 3.2 + */ + public void createLink(URI location, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this resource from the workspace. + *

            + * This is a convenience method, fully equivalent to: + *

            +	 *   delete((keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
            +	 * 
            + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this folder has been removed from its parent. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
              + *
            • This resource could not be deleted for some reason.
            • + *
            • This resource is out of sync with the local file system + * and force is false.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IResourceRuleFactory#deleteRule(IResource) + * @see IResource#delete(int,IProgressMonitor) + */ + public void delete(boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Returns a handle to the file with the given name in this folder. + *

            + * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

            + * + * @param name the string name of the member file + * @return the (handle of the) member file + * @see #getFolder(String) + */ + public IFile getFile(String name); + + /** + * Returns a handle to the folder with the given name in this folder. + *

            + * This is a resource handle operation; neither the container + * nor the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

            + * + * @param name the string name of the member folder + * @return the (handle of the) member folder + * @see #getFile(String) + */ + public IFolder getFolder(String name); + + /** + * Moves this resource so that it is located at the given path. + *

            + * This is a convenience method, fully equivalent to: + *

            +	 *   move(destination, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
            +	 * 
            + *

            + *

            + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this folder has been removed from its parent and a new folder + * has been added to the parent of the destination. + *

            + *

            + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

            + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
              + *
            • This resource does not exist.
            • + *
            • This resource or one of its descendents is not local.
            • + *
            • The resource corresponding to the parent destination path does not exist.
            • + *
            • The resource corresponding to the parent destination path is a closed + * project.
            • + *
            • A resource at destination path does exist.
            • + *
            • A resource of a different type exists at the destination path.
            • + *
            • This resource or one of its descendents is out of sync with the local file system + * and force is false.
            • + *
            • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
            • + *
            • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
            • + *
            + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @see IResource#move(IPath,int,IProgressMonitor) + */ + public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java new file mode 100644 index 0000000000..7672452315 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java @@ -0,0 +1,578 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Map; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; + +/** + * Markers are a general mechanism for associating notes and meta-data with + * resources. + *

            + * Markers themselves are handles in the same way as IResources + * are handles. Instances of IMarker do not hold the attributes + * themselves but rather uniquely refer to the attribute container. As such, + * their state may change underneath the handle with no warning to the holder + * of the handle. + *

            + * The Resources plug-in provides a general framework for + * defining and manipulating markers and provides several standard marker types. + *

            + *

            + * Each marker has:

              + *
            • a type string, specifying its type (e.g. + * "org.eclipse.core.resources.taskmarker"),
            • + *
            • an identifier which is unique (relative to a particular resource)
            • + *
            + * Specific types of markers may carry additional information. + *

            + *

            + * The resources plug-in defines five standard types: + *

              + *
            • org.eclipse.core.resources.marker
            • + *
            • org.eclipse.core.resources.taskmarker
            • + *
            • org.eclipse.core.resources.problemmarker
            • + *
            • org.eclipse.core.resources.bookmark
            • + *
            • org.eclipse.core.resources.textmarker
            • + *
            + * The plug-in also provides an extension point ( + * org.eclipse.core.resources.markers) into which other + * plug-ins can install marker type declaration extensions. + *

            + *

            + * Marker types are declared within a multiple inheritance type system. + * New markers are defined in the plugin.xml file of the + * declaring plug-in. A valid declaration contains elements as defined by + * the extension point DTD: + *

              + *
            • type - the unique name of the marker type
            • + *
            • super - the list of marker types of which this marker is to be considered a sub-type
            • + *
            • attributes - the list of standard attributes which may be present on this type of marker
            • + *
            • persistent - whether markers of this type should be persisted by the platform
            • + * + *

              + *

              All markers declared as persistent are saved when the + * workspace is saved, except those explicitly set as transient (the + * TRANSIENT attribute is set as true). A plug-in + * which defines a persistent marker is not directly involved in saving and + * restoring the marker. Markers are not under version and configuration + * management, and cannot be shared via VCM repositories. + *

              + *

              + * Markers implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

              + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IMarker extends IAdaptable { + + /*==================================================================== + * Marker types: + *====================================================================*/ + + /** + * Base marker type. + * + * @see #getType() + */ + public static final String MARKER = ResourcesPlugin.PI_RESOURCES + ".marker"; //$NON-NLS-1$ + + /** + * Task marker type. + * + * @see #getType() + */ + public static final String TASK = ResourcesPlugin.PI_RESOURCES + ".taskmarker"; //$NON-NLS-1$ + + /** + * Problem marker type. + * + * @see #getType() + */ + public static final String PROBLEM = ResourcesPlugin.PI_RESOURCES + ".problemmarker"; //$NON-NLS-1$ + + /** + * Text marker type. + * + * @see #getType() + */ + public static final String TEXT = ResourcesPlugin.PI_RESOURCES + ".textmarker"; //$NON-NLS-1$ + + /** + * Bookmark marker type. + * + * @see #getType() + */ + public static final String BOOKMARK = ResourcesPlugin.PI_RESOURCES + ".bookmark"; //$NON-NLS-1$ + + /*==================================================================== + * Marker attributes: + *====================================================================*/ + + /** + * Severity marker attribute. A number from the set of error, warning and info + * severities defined by the platform. + * + * @see #SEVERITY_ERROR + * @see #SEVERITY_WARNING + * @see #SEVERITY_INFO + * @see #getAttribute(String, int) + */ + public static final String SEVERITY = "severity"; //$NON-NLS-1$ + + /** + * Message marker attribute. A localized string describing the nature + * of the marker (e.g., a name for a bookmark or task). The content + * and form of this attribute is not specified or interpreted by the platform. + * + * @see #getAttribute(String, String) + */ + public static final String MESSAGE = "message"; //$NON-NLS-1$ + + /** + * Location marker attribute. The location is a human-readable (localized) string which + * can be used to distinguish between markers on a resource. As such it + * should be concise and aimed at users. The content and + * form of this attribute is not specified or interpreted by the platform. + * + * @see #getAttribute(String, String) + */ + public static final String LOCATION = "location"; //$NON-NLS-1$ + + /** + * Priority marker attribute. A number from the set of high, normal and low + * priorities defined by the platform. + * + * @see #PRIORITY_HIGH + * @see #PRIORITY_NORMAL + * @see #PRIORITY_LOW + * @see #getAttribute(String, int) + */ + public static final String PRIORITY = "priority"; //$NON-NLS-1$ + + /** + * Done marker attribute. A boolean value indicating whether + * the marker (e.g., a task) is considered done. + * + * @see #getAttribute(String, String) + */ + public static final String DONE = "done"; //$NON-NLS-1$ + + /** + * Character start marker attribute. An integer value indicating where a text + * marker starts. This attribute is zero-relative and inclusive. + * + * @see #getAttribute(String, String) + */ + public static final String CHAR_START = "charStart"; //$NON-NLS-1$ + + /** + * Character end marker attribute. An integer value indicating where a text + * marker ends. This attribute is zero-relative and exclusive. + * + * @see #getAttribute(String, String) + */ + public static final String CHAR_END = "charEnd"; //$NON-NLS-1$ + + /** + * Line number marker attribute. An integer value indicating the line number + * for a text marker. This attribute is 1-relative. + * + * @see #getAttribute(String, String) + */ + public static final String LINE_NUMBER = "lineNumber"; //$NON-NLS-1$ + + /** + * Transient marker attribute. A boolean value indicating whether the + * marker (e. g., a task) is considered transient even if its type is + * declared as persistent. + * + * @see #getAttribute(String, String) + * @since 2.1 + */ + public static final String TRANSIENT = "transient"; //$NON-NLS-1$ + + /** + * User editable marker attribute. A boolean value indicating whether a + * user should be able to manually change the marker (e.g. a task). The + * default is true. Note that the value of this attribute + * is to be used by the UI as a suggestion and its value will NOT be + * interpreted by Core in any manner and will not be enforced by Core + * when performing any operations on markers. + * + * @see #getAttribute(String, String) + * @since 2.1 + */ + public static final String USER_EDITABLE = "userEditable"; //$NON-NLS-1$ + + /** + * Source id attribute. A string attribute that can be used by tools that + * generate markers to indicate the source of the marker. Use of this attribute is + * optional and its format or existence is not enforced. This attribute is + * intended to improve serviceability by providing a value that product support + * personnel or automated tools can use to determine appropriate help and + * resolutions for markers. + * + * @see #getAttribute(String, String) + * @since 3.3 + */ + public static final String SOURCE_ID = "sourceId"; //$NON-NLS-1$ + + /*==================================================================== + * Marker attributes values: + *====================================================================*/ + + /** + * High priority constant (value 2). + * + * @see #getAttribute(String, int) + */ + public static final int PRIORITY_HIGH = 2; + + /** + * Normal priority constant (value 1). + * + * @see #getAttribute(String, int) + */ + public static final int PRIORITY_NORMAL = 1; + + /** + * Low priority constant (value 0). + * + * @see #getAttribute(String, int) + */ + public static final int PRIORITY_LOW = 0; + + /** + * Error severity constant (value 2) indicating an error state. + * + * @see #getAttribute(String, int) + */ + public static final int SEVERITY_ERROR = 2; + + /** + * Warning severity constant (value 1) indicating a warning. + * + * @see #getAttribute(String, int) + */ + public static final int SEVERITY_WARNING = 1; + + /** + * Info severity constant (value 0) indicating information only. + * + * @see #getAttribute(String, int) + */ + public static final int SEVERITY_INFO = 0; + + /** + * Deletes this marker from its associated resource. This method has no + * effect if this marker does not exist. + * + * @exception CoreException if this marker could not be deleted. Reasons include: + *
                + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void delete() throws CoreException; + + /** + * Tests this marker for equality with the given object. + * Two markers are equal if their id and resource are both equal. + * + * @param object the other object + * @return an indication of whether the objects are equal + */ + @Override + public boolean equals(Object object); + + /** + * Returns whether this marker exists in the workspace. A marker + * exists if its resource exists and has a marker with the marker's id. + * + * @return true if this marker exists, otherwise + * false + */ + public boolean exists(); + + /** + * Returns the attribute with the given name. The result is an instance of one + * of the following classes: String, Integer, + * or Boolean. + * Returns null if the attribute is undefined. + * + * @param attributeName the name of the attribute + * @return the value, or null if the attribute is undefined. + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This marker does not exist.
              • + *
              + */ + public Object getAttribute(String attributeName) throws CoreException; + + /** + * Returns the integer-valued attribute with the given name. + * Returns the given default value if the attribute is undefined. + * or the marker does not exist or is not an integer value. + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if no value is found + * @return the value or the default value if no value was found. + */ + public int getAttribute(String attributeName, int defaultValue); + + /** + * Returns the string-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or the marker does not exist or is not a string value. + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if no value is found + * @return the value or the default value if no value was found. + */ + public String getAttribute(String attributeName, String defaultValue); + + /** + * Returns the boolean-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or the marker does not exist or is not a boolean value. + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if no value is found + * @return the value or the default value if no value was found. + */ + public boolean getAttribute(String attributeName, boolean defaultValue); + + /** + * Returns a map with all the attributes for the marker. + * If the marker has no attributes then null is returned. + * + * @return a map of attribute keys and values (key type : String + * value type : String, Integer, or + * Boolean) or null. + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This marker does not exist.
              • + *
              + */ + public Map getAttributes() throws CoreException; + + /** + * Returns the attributes with the given names. The result is an an array + * whose elements correspond to the elements of the given attribute name + * array. Each element is null or an instance of one + * of the following classes: String, Integer, + * or Boolean. + * + * @param attributeNames the names of the attributes + * @return the values of the given attributes. + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This marker does not exist.
              • + *
              + */ + public Object[] getAttributes(String[] attributeNames) throws CoreException; + + /** + * Returns the time at which this marker was created. + * + * @return the difference, measured in milliseconds, between the time at which + * this marker was created and midnight, January 1, 1970 UTC, or 0L + * if the creation time is not known (this can occur in workspaces created using v2.0 or earlier). + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This marker does not exist.
              • + *
              + * @since 2.1 + */ + public long getCreationTime() throws CoreException; + + /** + * Returns the id of the marker. The id of a marker is unique + * relative to the resource with which the marker is associated. + * Marker ids are not globally unique. + * + * @return the id of the marker + * @see IResource#findMarker(long) + */ + public long getId(); + + /** + * Returns the resource with which this marker is associated. + * + * @return the resource with which this marker is associated + */ + public IResource getResource(); + + /** + * Returns the type of this marker. The returned marker type will not be + * null. + * + * @return the type of this marker + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This marker does not exist.
              • + *
              + */ + public String getType() throws CoreException; + + /** + * Returns whether the type of this marker is considered to be a sub-type of + * the given marker type. + * + * @return boolean trueif the marker's type + * is the same as (or a sub-type of) the given type. + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This marker does not exist.
              • + *
              + */ + public boolean isSubtypeOf(String superType) throws CoreException; + + /** + * Sets the integer-valued attribute with the given name. + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

              + * + * @param attributeName the name of the attribute + * @param value the value + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This marker does not exist.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttribute(String attributeName, int value) throws CoreException; + + /** + * Sets the attribute with the given name. The value must be null or + * an instance of one of the following classes: + * String, Integer, or Boolean. + * If the value is null, the attribute is considered to be undefined. + * + *

              + * The attribute value cannot be String + * whose UTF encoding exceeds 65535 bytes. On persistent + * markers this limit is enforced by an assertion. + *

              + * + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

              + * + * @param attributeName the name of the attribute + * @param value the value, or null if the attribute is to be undefined + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This marker does not exist.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttribute(String attributeName, Object value) throws CoreException; + + /** + * Sets the boolean-valued attribute with the given name. + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

              + * + * @param attributeName the name of the attribute + * @param value the value + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This marker does not exist.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttribute(String attributeName, boolean value) throws CoreException; + + /** + * Sets the given attribute key-value pairs on this marker. + * The values must be null or an instance of + * one of the following classes: String, + * Integer, or Boolean. + * If a value is null, the new value of the + * attribute is considered to be undefined. + * + *

              + * The values of the attributes cannot be String + * whose UTF encoding exceeds 65535 bytes. On persistent markers + * this limit is enforced by an assertion. + *

              + * + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

              + * + * @param attributeNames an array of attribute names + * @param values an array of attribute values + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This marker does not exist.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttributes(String[] attributeNames, Object[] values) throws CoreException; + + /** + * Sets the attributes for this marker to be the ones contained in the + * given table. The values must be an instance of one of the following classes: + * String, Integer, or Boolean. + * Attributes previously set on the marker but not included in the given map + * are considered to be removals. Setting the given map to be null + * is equivalent to removing all marker attributes. + * + *

              + * The values of the attributes cannot be String + * whose UTF encoding exceeds 65535 bytes. On persistent markers + * this limit is enforced by an assertion. + *

              + * + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

              + * + * @param attributes a map of attribute names to attribute values + * (key type : String value type : String, + * Integer, or Boolean) or null + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This marker does not exist.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttributes(Map attributes) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java new file mode 100644 index 0000000000..9c2e642233 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Map; + +/** + * A marker delta describes the change to a single marker. + * A marker can either be added, removed or changed. + * Marker deltas give access to the state of the marker as it + * was (in the case of deletions and changes) before the modifying + * operation occurred. + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IMarkerDelta { + /** + * Returns the object attribute with the given name. The result is an instance of one + * of the following classes: String, Integer, + * or Boolean. + * Returns null if the attribute is undefined. + * The set of valid attribute names is defined elsewhere. + *

              + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

              + * @param attributeName the name of the attribute + * @return the value, or null if the attribute is undefined. + */ + public Object getAttribute(String attributeName); + + /** + * Returns the integer-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or is not an integer value. + *

              + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

              + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if the attribute does not exist + * @return the value or the default value if the attribute is undefined. + */ + public int getAttribute(String attributeName, int defaultValue); + + /** + * Returns the string-valued attribute with the given name. + * Returns the given default value if the attribute is undefined or + * is not a string value. + *

              + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

              + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if the attribute does not exist + * @return the value or the default value if the attribute is undefined. + */ + public String getAttribute(String attributeName, String defaultValue); + + /** + * Returns the boolean-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or is not a boolean value. + *

              + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

              + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if the attribute does not exist + * @return the value or the default value if the attribute is undefined. + */ + public boolean getAttribute(String attributeName, boolean defaultValue); + + /** + * Returns a Map with all the attributes for the marker. The result is a Map + * whose keys are attributes names and whose values are attribute values. + * Each value an instance of one of the following classes: String, + * Integer, or Boolean. If the marker has no + * attributes then null is returned. + *

              + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

              + * + * @return a map of attribute keys and values (key type : String + * value type : String, Integer, or + * Boolean) or null. + */ + public Map getAttributes(); + + /** + * Returns the attributes with the given names. The result is an array + * whose elements correspond to the elements of the given attribute name + * array. Each element is null or an instance of one + * of the following classes: String, Integer, + * or Boolean. + *

              + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

              + * + * @param attributeNames the names of the attributes + * @return the values of the given attributes. + */ + public Object[] getAttributes(String[] attributeNames); + + /** + * Returns the id of the marker. The id of a marker is unique + * relative to the resource with which the marker is associated. + * Marker ids are not globally unique. + * + * @return the id of the marker + */ + public long getId(); + + /** + * Returns the kind of this marker delta: + * one of IResourceDelta.ADDED, + * IResourceDelta.REMOVED, or IResourceDelta.CHANGED. + * + * @return the kind of marker delta + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + */ + public int getKind(); + + /** + * Returns the marker described by this change. + * If kind is IResourceDelta.REMOVED, then this is the old marker, + * otherwise this is the new marker. Note that if the marker was deleted, + * the value returned cannot be used to access attributes. + * + * @return the marker + */ + public IMarker getMarker(); + + /** + * Returns the resource with which this marker is associated. + * + * @return the resource + */ + public IResource getResource(); + + /** + * Returns the type of this marker. + *

              + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

              + * + * @return the type of this marker + */ + public String getType(); + + /** + * Returns whether the type of this marker is considered to be a sub-type of + * the given marker type. + *

              + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

              + * + * @return boolean trueif the marker's type + * is the same as (or a sub-type of) the given type. + */ + public boolean isSubtypeOf(String superType); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java new file mode 100644 index 0000000000..c0138248e4 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; + +/** + * Describes a change in a path variable. The change may denote that a + * variable has been created, deleted or had its value changed. + * + * @since 2.1 + * @see IPathVariableChangeListener + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IPathVariableChangeEvent { + + /** Event type constant (value = 1) that denotes a value change . */ + public final static int VARIABLE_CHANGED = 1; + + /** Event type constant (value = 2) that denotes a variable creation. */ + public final static int VARIABLE_CREATED = 2; + + /** Event type constant (value = 3) that denotes a variable deletion. */ + public final static int VARIABLE_DELETED = 3; + + /** + * Returns the variable's current value. If the event type is + * VARIABLE_CHANGED then it is the new value, if the event + * type is VARIABLE_CREATED then it is the new value, or + * if the event type is VARIABLE_DELETED then it will + * be null. + * + * @return the variable's current value, or null + */ + public IPath getValue(); + + /** + * Returns the affected variable's name. + * + * @return the affected variable's name + */ + public String getVariableName(); + + /** + * Returns an object identifying the source of this event. + * + * @return an object identifying the source of this event + * @see java.util.EventObject + */ + public Object getSource(); + + /** + * Returns the type of event being reported. + * + * @return one of the event type constants + * @see #VARIABLE_CHANGED + * @see #VARIABLE_CREATED + * @see #VARIABLE_DELETED + */ + public int getType(); + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java new file mode 100644 index 0000000000..cd55441aef --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.util.EventListener; + +/** + * An interface to be implemented by objects interested in path variable + * creation, removal and value change events. + * + *

              Clients may implement this interface.

              + * + * @since 2.1 + */ +public interface IPathVariableChangeListener extends EventListener { + /** + * Notification that a path variable has changed. + *

              + * This method is called when a path variable is added, removed or has its value + * changed in the observed IPathVariableManager object. + *

              + * + * @param event the path variable change event object describing which variable + * changed and how + * @see IPathVariableManager#addChangeListener(IPathVariableChangeListener) + * @see IPathVariableManager#removeChangeListener(IPathVariableChangeListener) + * @see IPathVariableChangeEvent + */ + public void pathVariableChanged(IPathVariableChangeEvent event); + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java new file mode 100644 index 0000000000..c6c5ed92a3 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java @@ -0,0 +1,352 @@ +/******************************************************************************* + * 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] Project Path Variable Support + *******************************************************************************/ + +package org.eclipse.core.resources; + +import java.net.URI; +import org.eclipse.core.runtime.*; + +/** + * Manages a collection of path variables and resolves paths containing a + * variable reference. + *

              + * A path variable is a pair of non-null elements (name,value) where name is + * a case-sensitive string (containing only letters, digits and the underscore + * character, and not starting with a digit), and value is an absolute + * IPath object. + *

              + *

              + * Path variables allow for the creation of relative paths whose exact + * location in the file system depends on the value of a variable. A variable + * reference may only appear as the first segment of a relative path. + *

              + * + * @see org.eclipse.core.runtime.IPath + * @since 2.1 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IPathVariableManager { + + /** + * Converts an absolute path to path relative to some defined + * variable. For example, converts "C:/foo/bar.txt" into "FOO/bar.txt", + * granted that the path variable "FOO" value is "C:/foo". + *

              + * The "force" argument will cause an intermediate path variable to be created if + * the given path can be relative only to a parent of an existing path variable. + * For example, if the path "C:/other/file.txt" is to be converted + * and no path variables point to "C:/" or "C:/other" but "FOO" + * points to "C:/foo", an intermediate "OTHER" variable will be + * created relative to "FOO" containing the value "${PARENT-1-FOO}" + * so that the final path returned will be "OTHER/file.txt". + *

              + *

              + * The argument "variableHint" can be used to specify the name of the path + * variable to make the provided path relative to. + *

              + * + * @param path The absolute path to be converted + * @param force indicates whether intermediate path variables should be created + * if the path is relative only to a parent of an existing path variable. + * @param variableHint The name of the variable to which the path should be made + * relative to, or null for the nearest one. + * @return The converted path + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • The variable name is not valid
              • + *
              + * @since 3.6 + */ + public URI convertToRelative(URI path, boolean force, String variableHint) throws CoreException; + + /** + * Sets the path variable with the given name to be the specified value. + * Depending on the value given and if the variable is currently defined + * or not, there are several possible outcomes for this operation: + *

              + *

                + *
              • A new variable will be created, if there is no variable defined with + * the given name, and the given value is not null. + *
              • + * + *
              • The referred variable's value will be changed, if it already exists + * and the given value is not null.
              • + * + *
              • The referred variable will be removed, if a variable with the given + * name is currently defined and the given value is null. + *
              • + * + *
              • The call will be ignored, if a variable with the given name is not + * currently defined and the given value is null, or if it is + * defined but the given value is equal to its current value. + *
              • + *
              + *

              If a variable is effectively changed, created or removed by a call to + * this method, notification will be sent to all registered listeners.

              + * + * @param name the name of the variable + * @param value the value for the variable (may be null) + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • The variable name is not valid
              • + *
              • The variable value is relative
              • + *
              + * @deprecated use {@link #setURIValue(String, URI)} instead. + */ + @Deprecated + public void setValue(String name, IPath value) throws CoreException; + + /** + * Sets the path variable with the given name to be the specified value. + * Depending on the value given and if the variable is currently defined + * or not, there are several possible outcomes for this operation: + *

              + *

                + *
              • A new variable will be created, if there is no variable defined with + * the given name, and the given value is not null. + *
              • + * + *
              • The referred variable's value will be changed, if it already exists + * and the given value is not null.
              • + * + *
              • The referred variable will be removed, if a variable with the given + * name is currently defined and the given value is null. + *
              • + * + *
              • The call will be ignored, if a variable with the given name is not + * currently defined and the given value is null, or if it is + * defined but the given value is equal to its current value. + *
              • + *
              + *

              If a variable is effectively changed, created or removed by a call to + * this method, notification will be sent to all registered listeners.

              + * + * @param name the name of the variable + * @param value the value for the variable (may be null) + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • The variable name is not valid
              • + *
              • The variable value is relative
              • + *
              + * @since 3.6 + */ + public void setURIValue(String name, URI value) throws CoreException; + + /** + * Returns the value of the path variable with the given name. If there is + * no variable defined with the given name, returns null. + * + * @param name the name of the variable to return the value for + * @return the value for the variable, or null if there is no + * variable defined with the given name + * @deprecated use {@link #getURIValue(String)} instead. + */ + @Deprecated + public IPath getValue(String name); + + /** + * Returns the value of the path variable with the given name. If there is + * no variable defined with the given name, returns null. + * + * @param name the name of the variable to return the value for + * @return the value for the variable, or null if there is no + * variable defined with the given name + * @since 3.6 + */ + public URI getURIValue(String name); + + /** + * Returns an array containing all defined path variable names. + * + * @return an array containing all defined path variable names + */ + public String[] getPathVariableNames(); + + // Should be added for 3.6 + // public String[] getPathVariableNames(String name); + + /** + * Registers the given listener to receive notification of changes to path + * variables. The listener will be notified whenever a variable has been + * added, removed or had its value changed. Has no effect if an identical + * path variable change listener is already registered. + * + * @param listener the listener + * @see IPathVariableChangeListener + */ + public void addChangeListener(IPathVariableChangeListener listener); + + /** + * Removes the given path variable change listener from the listeners list. + * Has no effect if an identical listener is not registered. + * + * @param listener the listener + * @see IPathVariableChangeListener + */ + public void removeChangeListener(IPathVariableChangeListener listener); + + /** + * Resolves a relative URI object potentially containing a + * variable reference as its first segment, replacing the variable reference + * (if any) with the variable's value (which is a concrete absolute URI). + * If the given URI is absolute or has a non- null device then + * no variable substitution is done and that URI is returned as is. If the + * given URI is relative and has a null device, but the first + * segment does not correspond to a defined variable, then the URI is + * returned as is. + *

              + * If the given URI is null then null will be + * returned. In all other cases the result will be non-null. + *

              + * + * @param uri the URI to be resolved + * @return the resolved URI or null + * @since 3.2 + */ + public URI resolveURI(URI uri); + + /** + * Resolves a relative IPath object potentially containing a + * variable reference as its first segment, replacing the variable reference + * (if any) with the variable's value (which is a concrete absolute path). + * If the given path is absolute or has a non- null device then + * no variable substitution is done and that path is returned as is. If the + * given path is relative and has a null device, but the first + * segment does not correspond to a defined variable, then the path is + * returned as is. + *

              + * If the given path is null then null will be + * returned. In all other cases the result will be non-null. + *

              + * + *

              + * For example, consider the following collection of path variables: + *

              + *
                + *
              • TEMP = c:/temp
              • + *
              • BACKUP = /tmp/backup
              • + *
              + *

              The following paths would be resolved as: + *

              c:/bin => c:/bin

              + *

              c:TEMP => c:TEMP

              + *

              /TEMP => /TEMP

              + *

              TEMP => c:/temp

              + *

              TEMP/foo => c:/temp/foo

              + *

              BACKUP => /tmp/backup

              + *

              BACKUP/bar.txt => /tmp/backup/bar.txt

              + *

              SOMEPATH/foo => SOMEPATH/foo

              + * + * @param path the path to be resolved + * @return the resolved path or null + * @deprecated use {@link #resolveURI(URI)} instead. + */ + @Deprecated + public IPath resolvePath(IPath path); + + /** + * Returns true if the given variable is defined and + * false otherwise. Returns false if the given + * name is not a valid path variable name. + * + * @param name the variable's name + * @return true if the variable exists, false + * otherwise + */ + public boolean isDefined(String name); + + /** + * Returns whether a variable is user defined or not. + * + * @return true if the path is user defined. + * @since 3.6 + */ + public boolean isUserDefined(String name); + + /** + * Validates the given name as the name for a path variable. A valid path + * variable name is made exclusively of letters, digits and the underscore + * character, and does not start with a digit. + * + * @param name a possibly valid path variable name + * @return a status object with code IStatus.OK if + * the given name is a valid path variable name, otherwise a status + * object indicating what is wrong with the string + * @see IStatus#OK + */ + public IStatus validateName(String name); + + /** + * Validates the given path as the value for a path variable. A path + * variable value must be a valid path that is absolute. + * + * @param path a possibly valid path variable value + * @return a status object with code IStatus.OK if the given + * path is a valid path variable value, otherwise a status object indicating + * what is wrong with the value + * @see IPath#isValidPath(String) + * @see IStatus#OK + */ + public IStatus validateValue(IPath path); + + /** + * Validates the given path as the value for a path variable. A path + * variable value must be a valid path that is absolute. + * + * @param path a possibly valid path variable value + * @return a status object with code {@link IStatus#OK} if the given + * path is a valid path variable value, otherwise a status object indicating + * what is wrong with the value + * @see IPath#isValidPath(String) + * @see IStatus#OK + * @since 3.6 + */ + public IStatus validateValue(URI path); + + /** + * Returns a variable relative path equivalent to an absolute path for a + * file or folder in the file system, according to the variables defined in + * this project PathVariableManager. The file or folder need not to exist. + * + * @param location + * a path in the local file system + * @return the corresponding variable relative path, or null + * if no such path is available + * @since 3.6 + */ + public URI getVariableRelativePathLocation(URI location); + + /** + * Converts the internal format of the linked resource location if the PARENT + * variables is used. For example, if the value is "${PARENT-2-VAR}\foo", the + * converted result is "${VAR}\..\..\foo". + * @param value the value encoded using OS string (as returned from Path.toOSString()) + * @param locationFormat indicates whether the value contains a string that is stored in the linked resource location rather than in the path variable value + * @return the converted path variable value + * @since 3.6 + */ + public String convertToUserEditableFormat(String value, boolean locationFormat); + + /** + * Converts the user editable format to the internal format. + * For example, if the value is "${VAR}\..\..\foo", the + * converted result is "${PARENT-2-VAR}\foo". + * If the string is not directly convertible to a ${PARENT-COUNT-VAR} + * syntax (for example, the editable string "${FOO}bar\..\..\"), intermediate + * path variables will be created. + * @param value the value encoded using OS string (as returned from Path.toOSString()) + * @param locationFormat indicates whether the value contains a string that is stored in the linked resource location rather than in the path variable value + * @return the converted path variable value + * @since 3.6 + */ + public String convertFromUserEditableFormat(String value, boolean locationFormat); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java new file mode 100644 index 0000000000..9702a5bc21 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java @@ -0,0 +1,1055 @@ +/******************************************************************************* + * 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 - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import java.util.Map; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentTypeMatcher; + +/** + * A project is a type of resource which groups resources + * into buildable, reusable units. + *

              + * Features of projects include: + *

                + *
              • A project collects together a set of files and folders.
              • + *
              • A project's location controls where the project's resources are + * stored in the local file system.
              • + *
              • A project's build spec controls how building is done on the project.
              • + *
              • A project can carry session and persistent properties.
              • + *
              • A project can be open or closed; a closed project is + * passive and has a minimal in-memory footprint.
              • + *
              • A project can have one or more project build configurations.
              • + *
              • A project can carry references to other project build configurations.
              • + *
              • A project can have one or more project natures.
              • + *
              + *

              + *

              + * Projects implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

              + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IProject extends IContainer, IAdaptable { + /** + * Option constant (value 1) indicating that a snapshot to be + * loaded or saved contains a resource tree (refresh information). + * @see #loadSnapshot(int, URI, IProgressMonitor) + * @see #saveSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public static final int SNAPSHOT_TREE = 1; + + /** + * Invokes the build method of the specified builder + * for this project. Does nothing if this project is closed. If this project + * has multiple builders on its build spec matching the given name, only + * the first matching builder will be run. The build is run for the project's + * active build configuration. + *

              + * The builder name is declared in the extension that plugs in + * to the standard org.eclipse.core.resources.builders + * extension point. The arguments are builder specific. + *

              + *

              + * This method may change resources; these changes will be reported + * in a subsequent resource change event. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param kind the kind of build being requested. Valid values are: + *
                + *
              • {@link IncrementalProjectBuilder#FULL_BUILD}- indicates a full build.
              • + *
              • {@link IncrementalProjectBuilder#INCREMENTAL_BUILD}- indicates a incremental build.
              • + *
              • {@link IncrementalProjectBuilder#CLEAN_BUILD}- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states.
              • + *
              + * @param builderName the name of the builder + * @param args a table of builder-specific arguments keyed by argument name + * (key type: String, value type: String); + * null is equivalent to an empty map + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic {@link IResourceStatus#BUILD_FAILED} + * code, but it could also be any other status code; it might + * also be a {@link MultiStatus}. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IProjectDescription + * @see IncrementalProjectBuilder#build(int, Map, IProgressMonitor) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @see IResourceRuleFactory#buildRule() + */ + public void build(int kind, String builderName, Map args, IProgressMonitor monitor) throws CoreException; + + /** + * Builds this project. Does nothing if the project is closed. + *

              + * Building a project involves executing the commands found + * in this project's build spec. The build is run for the project's + * active build configuration. + *

              + *

              + * This method may change resources; these changes will be reported + * in a subsequent resource change event. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param kind the kind of build being requested. Valid values are: + *
                + *
              • IncrementalProjectBuilder.FULL_BUILD - indicates a full build.
              • + *
              • IncrementalProjectBuilder.INCREMENTAL_BUILD - indicates an incremental build.
              • + *
              • CLEAN_BUILD- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states. + *
              + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic BUILD_FAILED + * code, but it could also be any other status code; it might + * also be a multi-status. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IProjectDescription + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IResourceRuleFactory#buildRule() + */ + public void build(int kind, IProgressMonitor monitor) throws CoreException; + + /** + * Builds a specific build configuration of this project. Does nothing if the project is closed + * or the build configuration does not exist. + *

              + * Building a project involves executing the commands found + * in this project's build spec. The build is run for the specific project + * build configuration. + *

              + *

              + * This method may change resources; these changes will be reported + * in a subsequent resource change event. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * @param config build configuration to build + * @param kind the kind of build being requested. Valid values are: + *
                + *
              • IncrementalProjectBuilder.FULL_BUILD - indicates a full build.
              • + *
              • IncrementalProjectBuilder.INCREMENTAL_BUILD - indicates an incremental build.
              • + *
              • CLEAN_BUILD- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states. + *
              + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic BUILD_FAILED + * code, but it could also be any other status code; it might + * also be a multi-status. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IProjectDescription + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IResourceRuleFactory#buildRule() + * @since 3.7 + */ + public void build(IBuildConfiguration config, int kind, IProgressMonitor monitor) throws CoreException; + + /** + * Closes this project. The project need not be open. Closing + * a closed project does nothing. + *

              + * Closing a project involves ensuring that all important project-related + * state is safely stored on disk, and then discarding the in-memory + * representation of its resources and other volatile state, + * including session properties. + * After this method, the project continues to exist in the workspace + * but its member resources (and their members, etc.) do not. + * A closed project can later be re-opened. + *

              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event that includes + * an indication that this project has been closed and its members + * have been removed. + *

              + *

              + * 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 + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #open(IProgressMonitor) + * @see #isOpen() + * @see IResourceRuleFactory#modifyRule(IResource) + */ + public void close(IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new project resource in the workspace using the given project + * description. Upon successful completion, the project will exist but be closed. + *

              + * Newly created projects have no session or persistent properties. + *

              + *

              + * If the project content area given in the project description does not + * contain a project description file, a project description file is written + * in the project content area with the natures, build spec, comment, and + * referenced projects as specified in the given project description. + * If there is an existing project description file, it is not overwritten. + * In either case, this method does not cause natures to be configured. + *

              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project has been added to the workspace. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param description the project description + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project already exists in the workspace.
              • + *
              • The name of this resource is not valid (according to + * IWorkspace.validateName).
              • + *
              • The project location is not valid (according to + * IWorkspace.validateProjectLocation).
              • + *
              • The project description file could not be created in the project + * content area.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#validateProjectLocation(IProject, IPath) + * @see IResourceRuleFactory#createRule(IResource) + */ + public void create(IProjectDescription description, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new project resource in the workspace with files in the default + * location in the local file system. Upon successful completion, the project + * will exist but be closed. + *

              + * Newly created projects have no session or persistent properties. + *

              + *

              + * If the project content area does not contain a project description file, + * an initial project description file is written in the project content area + * with the following information: + *

                + *
              • no references to other projects
              • + *
              • no natures
              • + *
              • an empty build spec
              • + *
              • an empty comment
              • + *
              + * If there is an existing project description file, it is not overwritten. + *

              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this project has been added to the workspace. + *

              + *

              + * 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 + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project already exists in the workspace.
              • + *
              • The name of this resource is not valid (according to + * IWorkspace.validateName).
              • + *
              • The project location is not valid (according to + * IWorkspace.validateProjectLocation).
              • + *
              • The project description file could not be created in the project + * content area.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#validateProjectLocation(IProject, IPath) + * @see IResourceRuleFactory#createRule(IResource) + */ + public void create(IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new project resource in the workspace using the given project + * description. Upon successful completion, the project will exist but be closed. + *

              + * Newly created projects have no session or persistent properties. + *

              + *

              + * If the project content area given in the project description does not + * contain a project description file, a project description file is written + * in the project content area with the natures, build spec, comment, and + * referenced projects as specified in the given project description. + * If there is an existing project description file, it is not overwritten. + * In either case, this method does not cause natures to be configured. + *

              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project has been added to the workspace. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + *

              + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

              + *

              + * Update flags other than those listed above are ignored. + *

              + * + * @param description the project description + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project already exists in the workspace.
              • + *
              • The name of this resource is not valid (according to + * IWorkspace.validateName).
              • + *
              • The project location is not valid (according to + * IWorkspace.validateProjectLocation).
              • + *
              • The project description file could not be created in the project + * content area.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#validateProjectLocation(IProject, IPath) + * @see IResourceRuleFactory#createRule(IResource) + * + * @since 3.4 + */ + public void create(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this project from the workspace. + * No action is taken if this project does not exist. + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   delete(
              +	 *     (deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT )
              +	 *        | (force ? FORCE : IResource.NONE),
              +	 *     monitor);
              +	 * 
              + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param deleteContent a flag controlling how whether content is + * aggressively deleted + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project could not be deleted.
              • + *
              • This project's contents could not be deleted.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int, IProgressMonitor) + * @see #open(IProgressMonitor) + * @see #close(IProgressMonitor) + * @see IResource#delete(int,IProgressMonitor) + * @see IResourceRuleFactory#deleteRule(IResource) + */ + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the active build configuration for the project. + *

              + * If at any point the active configuration is removed from the project, for example + * when updating the list of build configurations, the active build configuration will be set to + * the first build configuration specified by {@link IProjectDescription#setBuildConfigs(String[])}. + *

              + * If all of the build configurations are removed, the active build configuration will be set to the + * default configuration. + *

              + * @return the active build configuration + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist.
              • + *
              • This project is not open.
              • + *
              + * @since 3.7 + */ + public IBuildConfiguration getActiveBuildConfig() throws CoreException; + + /** + * Returns the project {@link IBuildConfiguration} with the given name for this project. + * @param configName the name of the configuration to get + * @return a project configuration + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist.
              • + *
              • This project is not open.
              • + *
              • The configuration does not exist in this project.
              • + *
              + * @see #getBuildConfigs() + * @since 3.7 + */ + public IBuildConfiguration getBuildConfig(String configName) throws CoreException; + + /** + * Returns the build configurations for this project. A project always has at + * least one build configuration, so this will never return an empty list or null. + * The result will not contain duplicates. + * @return a list of project build configurations + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist.
              • + *
              • This project is not open.
              • + *
              + * @since 3.7 + */ + public IBuildConfiguration[] getBuildConfigs() throws CoreException; + + /** + * Returns this project's content type matcher. This content type matcher takes + * project specific preferences and nature-content type associations into + * account. + * + * @return the content type matcher for this project + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist.
              • + *
              • This project is not open.
              • + *
              + * @see IContentTypeMatcher + * @since 3.1 + */ + public IContentTypeMatcher getContentTypeMatcher() throws CoreException; + + /** + * Returns the description for this project. + * The returned value is a copy and cannot be used to modify + * this project. The returned value is suitable for use in creating, + * copying and moving other projects. + * + * @return the description for this project + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist.
              • + *
              • This project is not open.
              • + *
              + * @see #create(IProgressMonitor) + * @see #create(IProjectDescription, IProgressMonitor) + * @see IResource#copy(IProjectDescription, int, IProgressMonitor) + * @see #move(IProjectDescription, boolean, IProgressMonitor) + */ + public IProjectDescription getDescription() throws CoreException; + + /** + * Returns a handle to the file with the given name in this project. + *

              + * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

              + * + * @param name the string name of the member file + * @return the (handle of the) member file + * @see #getFolder(String) + */ + public IFile getFile(String name); + + /** + * Returns a handle to the folder with the given name in this project. + *

              + * This is a resource handle operation; neither the container + * nor the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

              + * + * @param name the string name of the member folder + * @return the (handle of the) member folder + * @see #getFile(String) + */ + public IFolder getFolder(String name); + + /** + * Returns the specified project nature for this project or null if + * the project nature has not been added to this project. + * Clients may downcast to a more concrete type for more nature-specific methods. + * The documentation for a project nature specifies any such additional protocol. + *

              + * This may cause the plug-in that provides the given nature to be activated. + *

              + * + * @param natureId the fully qualified nature extension identifier, formed + * by combining the nature extension id with the id of the declaring plug-in. + * (e.g. "com.example.acmeplugin.coolnature") + * @return the project nature object + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist.
              • + *
              • This project is not open.
              • + *
              • The project nature extension could not be found.
              • + *
              + */ + public IProjectNature getNature(String natureId) throws CoreException; + + /** + * Returns the location in the local file system of the project-specific + * working data area for use by the given plug-in or null + * if the project does not exist. + *

              + * The content, structure, and management of this area is + * the responsibility of the plug-in. This area is deleted when the + * project is deleted. + *

              + * This project needs to exist but does not need to be open. + *

              + * @param plugin the plug-in + * @return a local file system path + * @deprecated Use IProject.getWorkingLocation(plugin.getUniqueIdentifier()). + */ + @Deprecated + public IPath getPluginWorkingLocation(IPluginDescriptor plugin); + + /** + * Returns the location in the local file system of the project-specific + * working data area for use by the bundle/plug-in with the given identifier, + * or null if the project does not exist. + *

              + * The content, structure, and management of this area is + * the responsibility of the bundle/plug-in. This area is deleted when the + * project is deleted. + *

              + * This project needs to exist but does not need to be open. + *

              + * @param id the bundle or plug-in's identifier + * @return a local file system path + * @since 3.0 + */ + public IPath getWorkingLocation(String id); + + /** + * Returns the projects referenced by this project. This includes + * both the static and dynamic references of this project. + * The returned projects need not exist in the workspace. + * The result will not contain duplicates. Returns an empty + * array if there are no referenced projects. + * + * @return a list of projects + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist.
              • + *
              • This project is not open.
              • + *
              + * @see #getReferencedBuildConfigs(String, boolean) + * @see IProjectDescription#getReferencedProjects() + * @see IProjectDescription#getDynamicReferences() + */ + public IProject[] getReferencedProjects() throws CoreException; + + /** + * Returns the list of all open projects which reference + * this project. This project may or may not exist. Returns + * an empty array if there are no referencing projects. + * + * @return a list of open projects referencing this project + */ + public IProject[] getReferencingProjects(); + + /** + * Returns the build configurations referenced by the passed in build configuration + * on this project. + *

              + * This includes both the static and dynamic project level references. These are + * converted to build configurations pointing at the currently active referenced + * project configuration. + * The result will not contain duplicates. + *

              + *

              + * References to active configurations will be translated to references to actual + * build configurations, if the project is accessible. Note that if includeMissing + * is true BuildConfigurations which can't be resolved (i.e. exist on missing projects, + * or aren't listed on the referenced project) are still included in the returned + * IBuildConfiguration array. + *

              + *

              + * Returns an empty array if there are no references. + *

              + * + * @param configName the configuration to get the references for + * @param includeMissing boolean controls whether unresolved buildConfiguration should + * be included in the result + * @return an array of project build configurations + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist.
              • + *
              • This project is not open.
              • + *
              • The build configuration does not exist in this project.
              • + *
              + * @see IProjectDescription#getBuildConfigReferences(String) + * @since 3.7 + */ + public IBuildConfiguration[] getReferencedBuildConfigs(String configName, boolean includeMissing) throws CoreException; + + /** + * Checks whether the project has the specified build configuration. + * + * @param configName the configuration + * @return true if the project has the specified configuration, false otherwise + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist.
              • + *
              • This project is not open.
              • + *
              + * @since 3.7 + */ + public boolean hasBuildConfig(String configName) throws CoreException; + + /** + * Returns whether the project nature specified by the given + * nature extension id has been added to this project. + * + * @param natureId the nature extension identifier + * @return true if the project has the given nature + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist.
              • + *
              • This project is not open.
              • + *
              + */ + public boolean hasNature(String natureId) throws CoreException; + + /** + * Returns true if the project nature specified by the given + * nature extension id is enabled for this project, and false otherwise. + *

              + *

                Reasons for a nature not to be enabled include: + *
              • The nature is not available in the install.
              • + *
              • The nature has not been added to this project.
              • + *
              • The nature has a prerequisite that is not enabled + * for this project.
              • + *
              • The nature specifies "one-of-nature" membership in + * a set, and there is another nature on this project belonging + * to that set.
              • + *
              • The prerequisites for the nature form a cycle.
              • + *
              + *

              + * @param natureId the nature extension identifier + * @return true if the given nature is enabled for this project + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist.
              • + *
              • This project is not open.
              • + *
              + * @since 2.0 + * @see IWorkspace#validateNatureSet(String[]) + */ + public boolean isNatureEnabled(String natureId) throws CoreException; + + /** + * Returns whether this project is open. + *

              + * A project must be opened before it can be manipulated. + * A closed project is passive and has a minimal memory + * footprint; a closed project has no members. + *

              + * + * @return true if this project is open, false if + * this project is closed or does not exist + * @see #open(IProgressMonitor) + * @see #close(IProgressMonitor) + */ + public boolean isOpen(); + + /** + * Loads a snapshot of project meta-data from the given location URI. + * Must be called after the project has been created, but before it is + * opened. The options constant controls what kind of snapshot information + * to load. Valid option values include:
                + *
              • {@link IProject#SNAPSHOT_TREE} - load resource tree (refresh info) + *
              + * + * @param options kind of snapshot information to load + * @param snapshotLocation URI to load from + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • The snapshot was not found at the specified URI.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * @see #saveSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public void loadSnapshot(int options, URI snapshotLocation, + IProgressMonitor monitor) throws CoreException; + + /** + * Renames this project so that it is located at the name in + * the given description. + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   move(description, (force ? FORCE : IResource.NONE), monitor);
              +	 * 
              + *

              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource has been removed from its parent + * and that a corresponding resource has been added to its new parent. + * Additional information provided with resource delta shows that these + * additions and removals are related. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param description the description for the destination project + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                + *
              • This resource is not accessible.
              • + *
              • This resource or one of its descendents is not local.
              • + *
              • This resource or one of its descendents is out of sync with the local file system + * and force is false.
              • + *
              • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + * @see IResource#move(IProjectDescription,int,IProgressMonitor) + * @see IResourceRuleFactory#moveRule(IResource, IResource) + */ + public void move(IProjectDescription description, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Opens this project. No action is taken if the project is already open. + *

              + * Opening a project constructs an in-memory representation + * of its resources from information stored on disk. + *

              + *

              + * When a project is opened for the first time, initial information about the + * project's existing resources can be obtained in the following ways: + *

                + *
              • If a {@link #loadSnapshot(int, URI, IProgressMonitor)} call has been made + * before the open, resources are restored from that file (a file written by + * {@link #saveSnapshot(int, URI, IProgressMonitor)}). When the snapshot includes + * resource tree information and can be loaded without error, no refresh is initiated, + * so the project's resource tree will match what the snapshot provides. + *
              • Otherwise, when the {@link IResource#BACKGROUND_REFRESH} flag is specified, + * resources on disk will be added to the project in the background after + * this method returns. Child resources of the project may not be available + * until this background refresh completes. + *
              • Otherwise, resource information is obtained with a refresh operation in the + * foreground, before this method returns. + *
              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event that includes + * an indication that the project has been opened and its resources + * have been added to the tree. If the BACKGROUND_REFRESH + * update flag is specified, multiple resource change events may occur as + * resources on disk are discovered and added to the tree. + *

              + *

              + * 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 + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #close(IProgressMonitor) + * @see IResource#BACKGROUND_REFRESH + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 3.1 + */ + public void open(int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Opens this project. No action is taken if the project is already open. + *

              + * This is a convenience method, fully equivalent to + * open(IResource.NONE, monitor). + *

              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event that includes + * an indication that the project has been opened and its resources + * have been added to the tree. + *

              + *

              + * 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 + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #close(IProgressMonitor) + * @see IResourceRuleFactory#modifyRule(IResource) + */ + public void open(IProgressMonitor monitor) throws CoreException; + + /** + * Writes a snapshot of project meta-data into the given location URI. + * The options constant controls what kind of snapshot information to + * write. Valid option values include:
                + *
              • {@link IProject#SNAPSHOT_TREE} - save resource tree (refresh info) + *
              + * + * @param options kind of snapshot information to save + * @param snapshotLocation URI for saving the snapshot to + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • The URI is not writable or an error occurs writing the data.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * @see #loadSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public void saveSnapshot(int options, URI snapshotLocation, + IProgressMonitor monitor) throws CoreException; + + /** + * Changes this project resource to match the given project + * description. This project should exist and be open. + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   setDescription(description, KEEP_HISTORY, monitor);
              +	 * 
              + *

              + *

              + * This method requires the {@link IWorkspaceRoot} scheduling rule. + *

              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project's content has changed. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param description the project description + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist in the workspace.
              • + *
              • This project is not open.
              • + *
              • The location in the local file system corresponding to the project + * description file is occupied by a directory.
              • + *
              • The workspace is out of sync with the project description file + * in the local file system .
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              • The file modification validator disallowed the change.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see #getDescription() + * @see IProjectNature#configure() + * @see IProjectNature#deconfigure() + * @see #setDescription(IProjectDescription,int,IProgressMonitor) + */ + public void setDescription(IProjectDescription description, IProgressMonitor monitor) throws CoreException; + + /** + * Changes this project resource to match the given project + * description. This project should exist and be open. + *

              + * The given project description is used to change the project's + * natures, build spec, comment, and referenced projects. + * The name and location of a project cannot be changed using this method; + * these settings in the project description are ignored. To change a project's + * name or location, use {@link IResource#move(IProjectDescription, int, IProgressMonitor)}. + * The project's session and persistent properties are not affected. + *

              + *

              + * If the new description includes nature ids of natures that the project + * did not have before, these natures will be configured in automatically, + * which involves instantiating the project nature and calling + * {@link IProjectNature#configure()} on it. An internal reference to the + * nature object is retained, and will be returned on subsequent calls to + * getNature for the specified nature id. Similarly, any natures + * the project had which are no longer required will be automatically + * de-configured by calling {@link IProjectNature#deconfigure} + * on the nature object and letting go of the internal reference to it. + *

              + *

              + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite the project's description file in the local file system + * provided it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write the project description file in the local file system, overwriting + * any existing one if need be. + *

              + *

              + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of the project description file should be captured in the + * workspace's local history. The local history mechanism serves as a safety net + * to help the user recover from mistakes that might otherwise result in data + * loss. Specifying KEEP_HISTORY is recommended. Note that local + * history is maintained with each individual project, and gets discarded when + * a project is deleted from the workspace. + *

              + *

              + * The AVOID_NATURE_CONFIG update flag controls whether or + * not added and removed natures should be configured or de-configured. If this + * flag is not specified, then added natures will be configured and removed natures + * will be de-configured. If this flag is specified, natures can still be added or + * removed, but they will not be configured or de-configured. + *

              + *

              + * The scheduling rule required for this operation depends on the + * AVOID_NATURE_CONFIG flag. If the flag is specified the + * {@link IResourceRuleFactory#modifyRule} is required; If the flag is not specified, + * the {@link IWorkspaceRoot} scheduling rule is required. + *

              + *

              + * Update flags other than FORCE, KEEP_HISTORY, + * and AVOID_NATURE_CONFIG are ignored. + *

              + *

              + * Prior to modifying the project description file, the file modification + * validator (if provided by the Team plug-in), will be given a chance to + * perform any last minute preparations. Validation is performed by calling + * IFileModificationValidator.validateSave on the project + * description file. If the validation fails, then this operation will fail. + *

              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project's content has changed. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param description the project description + * @param updateFlags bit-wise or of update flag constants + * (FORCE, KEEP_HISTORY and + * AVOID_NATURE_CONFIG) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This project does not exist in the workspace.
              • + *
              • This project is not open.
              • + *
              • The location in the local file system corresponding to the project + * description file is occupied by a directory.
              • + *
              • The workspace is not in sync with the project + * description file in the local file system and FORCE is not + * specified.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              • The file modification validator disallowed the change.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see #getDescription() + * @see IProjectNature#configure() + * @see IProjectNature#deconfigure() + * @see IResource#FORCE + * @see IResource#KEEP_HISTORY + * @see IResource#AVOID_NATURE_CONFIG + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void setDescription(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java new file mode 100644 index 0000000000..8cb8f08c05 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java @@ -0,0 +1,385 @@ +/******************************************************************************* + * 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 + * Broadcom Corporation - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A project description contains the meta-data required to define + * a project. In effect, a project description is a project's "content". + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IProjectDescription { + /** + * Constant that denotes the name of the project description file (value + * ".project"). + * The handle of a project's description file is + * project.getFile(DESCRIPTION_FILE_NAME). + * The project description file is located in the root of the project's content area. + * + * @since 2.0 + */ + public static final String DESCRIPTION_FILE_NAME = ".project"; //$NON-NLS-1$ + + /** + * Returns the build configurations referenced by the specified configuration for the + * described project. + *

              + * These references are persisted by the workspace in a private location outside the + * project description file, and as such will not be shared when a project is exported + * or persisted in a repository. As such clients are always + * responsible for setting these references when a project is created or recreated. + *

              + *

              + * The referenced build configurations need not exist in the workspace. + * The result will not contain duplicates. The order of the references is preserved + * from the call to {@link #setBuildConfigReferences(String, IBuildConfiguration[])}. + * Returns an empty array if the provided config doesn't dynamically reference + * any other build configurations, or the given config does not exist in this description. + *

              + * @param configName the configuration in the described project to get the references for + * @return a list of dynamic build configurations + * @see #setBuildConfigReferences(String, IBuildConfiguration[]) + * @since 3.7 + */ + public IBuildConfiguration[] getBuildConfigReferences(String configName); + + /** + * Returns the list of build commands to run when building the described project. + * The commands are listed in the order in which they are to be run. + * + * @return the list of build commands for the described project + */ + public ICommand[] getBuildSpec(); + + /** + * Returns the descriptive comment for the described project. + * + * @return the comment for the described project + */ + public String getComment(); + + /** + * Returns the dynamic project references for the described project. Dynamic + * project references can be used instead of simple project references in cases + * where the reference information is computed dynamically be a third party. + * These references are persisted by the workspace in a private location outside + * the project description file, and as such will not be shared when a project is + * exported or persisted in a repository. A client using project references + * is always responsible for setting these references when a project is created + * or recreated. + *

              + * The returned projects need not exist in the workspace. The result will not + * contain duplicates. Returns an empty array if there are no dynamic project + * references on this description. + * + * @see #getBuildConfigReferences(String) + * @see #getReferencedProjects() + * @see #setDynamicReferences(IProject[]) + * @return a list of projects + * @since 3.0 + */ + public IProject[] getDynamicReferences(); + + /** + * Returns the local file system location for the described project. The path + * will be either an absolute file system path, or a relative path whose first + * segment is the name of a workspace path variable. null is + * returned if the default location should be used. This method will return + * null if this project is not located in the local file system. + * + * @return the location for the described project or null + * @deprecated Since 3.2, project locations are not necessarily in the local file + * system. The more general {@link #getLocationURI()} method should be used instead. + */ + @Deprecated + public IPath getLocation(); + + /** + * Returns the location URI for the described project. null is + * returned if the default location should be used. + * + * @return the location for the described project or null + * @since 3.2 + * @see #setLocationURI(URI) + */ + public URI getLocationURI(); + + /** + * Returns the name of the described project. + * + * @return the name of the described project + */ + public String getName(); + + /** + * Returns the list of natures associated with the described project. + * Returns an empty array if there are no natures on this description. + * + * @return the list of natures for the described project + * @see #setNatureIds(String[]) + */ + public String[] getNatureIds(); + + /** + * Returns the projects referenced by the described project. These references + * are persisted in the project description file (".project") and as such + * will be shared whenever the project is exported to another workspace. For + * references that are likely to change from one workspace to another, dynamic + * references should be used instead. + *

              + * The projects need not exist in the workspace. + * The result will not contain duplicates. Returns an empty + * array if there are no referenced projects on this description. + * + * @see #getDynamicReferences() + * @see #getBuildConfigReferences(String) + * @return a list of projects + */ + public IProject[] getReferencedProjects(); + + /** + * Returns whether the project nature specified by the given + * nature extension id has been added to the described project. + * + * @param natureId the nature extension identifier + * @return true if the described project has the given nature + */ + public boolean hasNature(String natureId); + + /** + * Returns a new build command. + *

              + * Note that the new command does not become part of this project + * description's build spec until it is installed via the setBuildSpec + * method. + *

              + * + * @return a new command + * @see #setBuildSpec(ICommand[]) + */ + public ICommand newCommand(); + + /** + * Sets the active configuration for the described project. + *

              + * If a configuration with the specified name does not exist in the project then the + * first configuration in the project is treated as the active configuration. + *

              + * + * @param configName the configuration to set as the active or default + * @since 3.7 + */ + public void setActiveBuildConfig(String configName); + + /** + * Sets the build configurations for the described project. + *

              + * The passed in names must all be non-null. + * Before they are set, duplicates are removed. + *

              + *

              + * All projects have one default build configuration, and it is impossible to configure + * the project to have no build configurations. + * If the input is null or an empty list, the current configurations are removed, + * and a default build configuration is (re-)added. + *

              + *

              + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

              + * + * @param configNames the configurations to set for the described project + * @see IProject#getActiveBuildConfig() + * @see IProject#getBuildConfigs() + * @see IProjectDescription#setActiveBuildConfig(String) + * @since 3.7 + */ + public void setBuildConfigs(String[] configNames); + + /** + * Sets the build configurations referenced by the specified configuration. + *

              + * The configuration to which references are being added needs to exist in this + * description, but the referenced projects and build configurations need not exist. + * A reference with null configuration name is resolved to the active build configuration + * on use. + * Duplicates will be removed. The order of the referenced build configurations is preserved. + * If the given configuration does not exist in this description then this has no effect. + *

              + *

              + * References at the build configuration level take precedence over references at the project level. + *

              + *

              + * Like dynamic references, these build configuration references are persisted as part of workspace + * metadata. + *

              + *

              + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

              + * + * @see #getBuildConfigReferences(String) + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @param configName the configuration in the described project to set the references for + * @param references list of build configuration references + * @since 3.7 + */ + public void setBuildConfigReferences(String configName, IBuildConfiguration[] references); + + /** + * Sets the list of build command to run when building the described project. + *

              + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

              + * + * @param buildSpec the array of build commands to run + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getBuildSpec() + * @see #newCommand() + */ + public void setBuildSpec(ICommand[] buildSpec); + + /** + * Sets the comment for the described project. + *

              + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

              + * + * @param comment the comment for the described project + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getComment() + */ + public void setComment(String comment); + + /** + * Sets the dynamic project references for the described project. + * The projects need not exist in the workspace. Duplicates will be + * removed. + *

              + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

              + * @see #getDynamicReferences() + * @see #setBuildConfigReferences(String, IBuildConfiguration[]) + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @param projects list of projects + * @since 3.0 + */ + public void setDynamicReferences(IProject[] projects); + + /** + * Sets the local file system location for the described project. The path must + * be either an absolute file system path, or a relative path whose first + * segment is the name of a defined workspace path variable. If + * null is specified, the default location is used. + *

              + * Setting the location on a description for a project which already + * exists has no effect; the new project location is ignored when the + * description is set on the already existing project. This method is + * intended for use on descriptions for new projects or for destination + * projects for copy and move. + *

              + *

              + * This operation maps the root folder of the project to the exact location + * provided. For example, if the location for project named "P" is set + * to the path c:\my_plugins\Project1, the file resource at workspace path + * /P/index.html would be stored in the local file system at + * c:\my_plugins\Project1\index.html. + *

              + * + * @param location the location for the described project or null + * @see #getLocation() + */ + public void setLocation(IPath location); + + /** + * Sets the location for the described project. + * If null is specified, the default location is used. + *

              + * Setting the location on a description for a project which already + * exists has no effect; the new project location is ignored when the + * description is set on the already existing project. This method is + * intended for use on descriptions for new projects or for destination + * projects for copy and move. + *

              + *

              + * This operation maps the root folder of the project to the exact location + * provided. For example, if the location for project named "P" is set + * to the URI file://c:/my_plugins/Project1, the file resource at workspace path + * /P/index.html would be stored in the local file system at + * file://c:/my_plugins/Project1/index.html. + *

              + * + * @param location the location for the described project or null + * @see #getLocationURI() + * @see IWorkspace#validateProjectLocationURI(IProject, URI) + * @since 3.2 + */ + public void setLocationURI(URI location); + + /** + * Sets the name of the described project. + *

              + * Setting the name on a description and then setting the + * description on the project has no effect; the new name is ignored. + *

              + *

              + * Creating a new project with a description name which doesn't + * match the project handle name results in the description name + * being ignored; the project will be created using the name + * in the handle. + *

              + * + * @param projectName the name of the described project + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getName() + */ + public void setName(String projectName); + + /** + * Sets the list of natures associated with the described project. + * A project created with this description will have these natures + * added to it in the given order. + *

              + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

              + * + * @param natures the list of natures + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getNatureIds() + */ + public void setNatureIds(String[] natures); + + /** + * Sets the referenced projects, ignoring any duplicates. + * The order of projects is preserved. + * The projects need not exist in the workspace. + *

              + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

              + * + * @param projects a list of projects + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #setBuildConfigReferences(String, IBuildConfiguration[]) + * @see #getReferencedProjects() + */ + public void setReferencedProjects(IProject[] projects); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java new file mode 100644 index 0000000000..aa649adb24 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * Interface for project nature runtime classes. + * It can configure a project with the project nature, or de-configure it. + * When a project is configured with a project nature, this is + * recorded in the list of project natures on the project. + * Individual project natures may expose a more specific runtime type, + * with additional API for manipulating the project in a + * nature-specific way. + *

              + * Clients may implement this interface. + *

              + * + * @see IProject#getNature(String) + * @see IProject#hasNature(String) + * @see IProjectDescription#getNatureIds() + * @see IProjectDescription#hasNature(String) + * @see IProjectDescription#setNatureIds(String[]) + */ +public interface IProjectNature { + /** + * Configures this nature for its project. This is called by the workspace + * when natures are added to the project using IProject.setDescription + * and should not be called directly by clients. The nature extension + * id is added to the list of natures before this method is called, + * and need not be added here. + * + * Exceptions thrown by this method will be propagated back to the caller + * of IProject.setDescription, but the nature will remain in + * the project description. + * + * @exception CoreException if this method fails. + */ + public void configure() throws CoreException; + + /** + * De-configures this nature for its project. This is called by the workspace + * when natures are removed from the project using + * IProject.setDescription and should not be called directly by + * clients. The nature extension id is removed from the list of natures before + * this method is called, and need not be removed here. + * + * Exceptions thrown by this method will be propagated back to the caller + * of IProject.setDescription, but the nature will still be + * removed from the project description. + * * + * @exception CoreException if this method fails. + */ + public void deconfigure() throws CoreException; + + /** + * Returns the project to which this project nature applies. + * + * @return the project handle + */ + public IProject getProject(); + + /** + * Sets the project to which this nature applies. + * Used when instantiating this project nature runtime. + * This is called by IProject.create() or + * IProject.setDescription() + * and should not be called directly by clients. + * + * @param project the project to which this nature applies + */ + public void setProject(IProject project); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java new file mode 100644 index 0000000000..cdd84de7eb --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +/** + * A project nature descriptor contains information about a project nature + * obtained from the plug-in manifest (plugin.xml) file. + *

              + * Nature descriptors are platform-defined objects that exist + * independent of whether that nature's plug-in has been started. + * In contrast, a project nature's runtime object (IProjectNature) + * generally runs plug-in-defined code. + *

              + * + * @see IProjectNature + * @see IWorkspace#getNatureDescriptor(String) + * @since 2.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 IProjectNatureDescriptor { + /** + * Returns the unique identifier of this nature. + *

              + * The nature identifier is composed of the nature's plug-in id and the simple + * id of the nature extension. For example, if plug-in "com.xyz" + * defines a nature extension with id "myNature", the unique + * nature identifier will be "com.xyz.myNature". + *

              + * @return the unique nature identifier + */ + public String getNatureId(); + + /** + * Returns a displayable label for this nature. + * Returns the empty string if no label for this nature + * is specified in the plug-in manifest file. + *

              Note that any translation specified in the plug-in manifest + * file is automatically applied. + *

              + * + * @return a displayable string label for this nature, + * possibly the empty string + */ + public String getLabel(); + + /** + * Returns the unique identifiers of the natures required by this nature. + * Nature requirements are specified by the "requires-nature" + * element on a nature extension. + * Returns an empty array if no natures are required by this nature. + * + * @return an array of nature ids that this nature requires, + * possibly an empty array. + */ + public String[] getRequiredNatureIds(); + + /** + * Returns the identifiers of the nature sets that this nature belongs to. + * Nature set inclusion is specified by the "one-of-nature" + * element on a nature extension. + * Returns an empty array if no nature sets are specified for this nature. + * + * @return an array of nature set ids that this nature belongs to, + * possibly an empty array. + */ + public String[] getNatureSetIds(); + + /** + * Returns whether this project nature allows linked resources to be created + * in projects where this nature is installed. + * + * @return boolean true if creating links is allowed, + * and false otherwise. + * @see IFolder#createLink(org.eclipse.core.runtime.IPath, int, org.eclipse.core.runtime.IProgressMonitor) + * @see IFile#createLink(org.eclipse.core.runtime.IPath, int, org.eclipse.core.runtime.IProgressMonitor) + * @since 2.1 + */ + public boolean isLinkingAllowed(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java new file mode 100644 index 0000000000..c9a4ae4e74 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java @@ -0,0 +1,2805 @@ +/******************************************************************************* + * 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 + * Red Hat Incorporated - get/setResourceAttribute code + * Oakland Software Incorporated - added getSessionProperties and getPersistentProperties + * Serge Beauchamp (Freescale Semiconductor) - [252996] add hasFilters() + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import java.util.Map; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * The workspace analog of file system files + * and directories. There are exactly four types of resource: + * files, folders, projects and the workspace root. + *

              + * File resources are similar to files in that they + * hold data directly. Folder resources are analogous to directories in that they + * hold other resources but cannot directly hold data. Project resources + * group files and folders into reusable clusters. The workspace root is the + * top level resource under which all others reside. + *

              + *

              + * Features of resources: + *

                + *
              • IResource objects are handles to state maintained + * by a workspace. That is, resource objects do not actually contain data + * themselves but rather represent resource state and give it behavior. Programmers + * are free to manipulate handles for resources that do not exist in a workspace + * but must keep in mind that some methods and operations require that an actual + * resource be available.
              • + *
              • Resources have two different kinds of properties as detailed below. All + * properties are keyed by qualified names.
              • + *
                  + *
                • Session properties: Session properties live for the lifetime of one execution of + * the workspace. They are not stored on disk. They can carry arbitrary + * object values. Clients should be aware that these values are kept in memory + * at all times and, as such, the values should not be large.
                • + *
                • Persistent properties: Persistent properties have string values which are stored + * on disk across platform sessions. The value of a persistent property is a + * string which should be short (i.e., under 2KB).
                • + *
                + * + *
              • Resources are identified by type and by their path, which is similar to a file system + * path. The name of a resource is the last segment of its path. A resource's parent + * is located by removing the last segment (the resource's name) from the resource's full path.
              • + *
              • Resources can be local or non-local. A non-local resource is one whose + * contents and properties have not been fetched from a repository.
              • + *
              • Phantom resources represent incoming additions or outgoing deletions + * which have yet to be reconciled with a synchronization partner.
              • + *
              + *

              + *

              + * Resources implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

              + * + * @see IWorkspace + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResource extends IAdaptable, ISchedulingRule { + + /*==================================================================== + * Constants defining resource types: There are four possible resource types + * and their type constants are in the integer range 1 to 8 as defined below. + *====================================================================*/ + + /** + * Type constant (bit mask value 1) which identifies file resources. + * + * @see IResource#getType() + * @see IFile + */ + public static final int FILE = 0x1; + + /** + * Type constant (bit mask value 2) which identifies folder resources. + * + * @see IResource#getType() + * @see IFolder + */ + public static final int FOLDER = 0x2; + + /** + * Type constant (bit mask value 4) which identifies project resources. + * + * @see IResource#getType() + * @see IProject + */ + public static final int PROJECT = 0x4; + + /** + * Type constant (bit mask value 8) which identifies the root resource. + * + * @see IResource#getType() + * @see IWorkspaceRoot + */ + public static final int ROOT = 0x8; + + /*==================================================================== + * Constants defining the depth of resource tree traversal: + *====================================================================*/ + + /** + * Depth constant (value 0) indicating this resource, but not any of its members. + */ + public static final int DEPTH_ZERO = 0; + + /** + * Depth constant (value 1) indicating this resource and its direct members. + */ + public static final int DEPTH_ONE = 1; + + /** + * Depth constant (value 2) indicating this resource and its direct and + * indirect members at any depth. + */ + public static final int DEPTH_INFINITE = 2; + + /*==================================================================== + * Constants for update flags for delete, move, copy, open, etc.: + *====================================================================*/ + + /** + * Update flag constant (bit mask value 1) indicating that the operation + * should proceed even if the resource is out of sync with the local file + * system. + * + * @since 2.0 + */ + public static final int FORCE = 0x1; + + /** + * Update flag constant (bit mask value 2) indicating that the operation + * should maintain local history by taking snapshots of the contents of + * files just before being overwritten or deleted. + * + * @see IFile#getHistory(IProgressMonitor) + * @since 2.0 + */ + public static final int KEEP_HISTORY = 0x2; + + /** + * Update flag constant (bit mask value 4) indicating that the operation + * should delete the files and folders of a project. + *

              + * Deleting a project that is open ordinarily deletes all its files and folders, + * whereas deleting a project that is closed retains its files and folders. + * Specifying ALWAYS_DELETE_PROJECT_CONTENT indicates that the contents + * of a project are to be deleted regardless of whether the project is open or closed + * at the time; specifying NEVER_DELETE_PROJECT_CONTENT indicates that + * the contents of a project are to be retained regardless of whether the project + * is open or closed at the time. + *

              + * + * @see #NEVER_DELETE_PROJECT_CONTENT + * @since 2.0 + */ + public static final int ALWAYS_DELETE_PROJECT_CONTENT = 0x4; + + /** + * Update flag constant (bit mask value 8) indicating that the operation + * should preserve the files and folders of a project. + *

              + * Deleting a project that is open ordinarily deletes all its files and folders, + * whereas deleting a project that is closed retains its files and folders. + * Specifying ALWAYS_DELETE_PROJECT_CONTENT indicates that the contents + * of a project are to be deleted regardless of whether the project is open or closed + * at the time; specifying NEVER_DELETE_PROJECT_CONTENT indicates that + * the contents of a project are to be retained regardless of whether the project + * is open or closed at the time. + *

              + * + * @see #ALWAYS_DELETE_PROJECT_CONTENT + * @since 2.0 + */ + public static final int NEVER_DELETE_PROJECT_CONTENT = 0x8; + + /** + * Update flag constant (bit mask value 16) indicating that the link creation + * should proceed even if the local file system file or directory is missing. + * + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @since 2.1 + */ + public static final int ALLOW_MISSING_LOCAL = 0x10; + + /** + * Update flag constant (bit mask value 32) indicating that a copy or move + * operation should only copy the link, rather than copy the underlying + * contents of the linked resource. + * + * @see #copy(IPath, int, IProgressMonitor) + * @see #move(IPath, int, IProgressMonitor) + * @since 2.1 + */ + public static final int SHALLOW = 0x20; + + /** + * Update flag constant (bit mask value 64) indicating that setting the + * project description should not attempt to configure and de-configure + * natures. + * + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @since 3.0 + */ + public static final int AVOID_NATURE_CONFIG = 0x40; + + /** + * Update flag constant (bit mask value 128) indicating that opening a + * project for the first time or creating a linked folder should refresh in the + * background. + * + * @see IProject#open(int, IProgressMonitor) + * @see IFolder#createLink(URI, int, IProgressMonitor) + * @since 3.1 + */ + public static final int BACKGROUND_REFRESH = 0x80; + + /** + * Update flag constant (bit mask value 256) indicating that a + * resource should be replaced with a resource of the same name + * at a different file system location. + * + * @see IFile#createLink(URI, int, IProgressMonitor) + * @see IFolder#createLink(URI, int, IProgressMonitor) + * @see IResource#move(IProjectDescription, int, IProgressMonitor) + * @since 3.2 + */ + public static final int REPLACE = 0x100; + + /** + * Update flag constant (bit mask value 512) indicating that ancestor + * resources of the target resource should be checked. + * + * @see IResource#isLinked(int) + * @since 3.2 + */ + public static final int CHECK_ANCESTORS = 0x200; + + /** + * Update flag constant (bit mask value 0x400) indicating that a + * resource should be marked as derived. + * + * @see IFile#create(java.io.InputStream, int, IProgressMonitor) + * @see IFolder#create(int, boolean, IProgressMonitor) + * @see IResource#setDerived(boolean) + * @since 3.2 + */ + public static final int DERIVED = 0x400; + + /** + * Update flag constant (bit mask value 0x800) indicating that a + * resource should be marked as team private. + * + * @see IFile#create(java.io.InputStream, int, IProgressMonitor) + * @see IFolder#create(int, boolean, IProgressMonitor) + * @see IResource#copy(IPath, int, IProgressMonitor) + * @see IResource#setTeamPrivateMember(boolean) + * @since 3.2 + */ + public static final int TEAM_PRIVATE = 0x800; + + /** + * Update flag constant (bit mask value 0x1000) indicating that a + * resource should be marked as a hidden resource. + * + * @since 3.4 + */ + public static final int HIDDEN = 0x1000; + + /** + * Update flag constant (bit mask value 0x2000) indicating that a + * resource should be marked as a virtual resource. + * + * @see IFolder#create(int, boolean, IProgressMonitor) + * @since 3.6 + */ + public static final int VIRTUAL = 0x2000; + + /*==================================================================== + * Other constants: + *====================================================================*/ + + /** + * Modification stamp constant (value -1) indicating no modification stamp is + * available. + * + * @see #getModificationStamp() + */ + public static final int NULL_STAMP = -1; + + /** + * General purpose zero-valued bit mask constant. Useful whenever you need to + * supply a bit mask with no bits set. + *

              + * Example usage: + * + *

              +	 *    delete(IResource.NONE, null)
              +	 * 
              + * + *

              + * + * @since 2.0 + */ + public static final int NONE = 0; + + /** + * Accepts the given visitor for an optimized traversal. + * The visitor's visit method is called, and is provided with a + * proxy to this resource. The proxy is a transient object that can be queried + * very quickly for information about the resource. If the actual resource + * handle is needed, it can be obtained from the proxy. Requesting the resource + * handle, or the full path of the resource, will degrade performance of the + * visit. + *

              + * The entire subtree under the given resource is traversed to infinite depth, + * unless the visitor ignores a subtree by returning false from its + * visit method. + *

              + *

              + * This is a convenience method, fully equivalent to + * accept(visitor, IResource.DEPTH_INFINITE, memberFlags). + *

              + *

              No guarantees are made about the behavior of this method if resources + * are deleted or added during the traversal of this resource hierarchy. If + * resources are deleted during the traversal, they may still be passed to the + * visitor; if resources are created, they may not be passed to the visitor. If + * resources other than the one being visited are modified during the traversal, + * the resource proxy may contain stale information when that resource is + * visited. + *

              +

              + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified in the member + * flags (recommended), only member resources that exist will be visited. + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is specified, the visit will + * also include any phantom member resource that the workspace is keeping track of. + *

              + *

              + * If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not specified + * (recommended), team private members will not be visited. If the + * {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the member + * flags, team private member resources are visited as well. + *

              + *

              + * If the {@link IContainer#INCLUDE_HIDDEN} flag is not specified (recommended), + * hidden resources will not be visited. If the {@link IContainer#INCLUDE_HIDDEN} flag is specified + * in the member flags, hidden resources are visited as well. + *

              + *

              + * If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified (recommended), + * the resource is checked for existence before the visitor's visit + * method is called. If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is specified + * in the member flags, the resource is not checked for existence before the visitor's + * visit method is called. Children of the resource are never checked + * for existence. + *

              + * + * @param visitor the visitor + * @param memberFlags bit-wise or of member flag constants + * ({@link IContainer#INCLUDE_PHANTOMS}, {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} + * and {@link IContainer#INCLUDE_HIDDEN}) indicating which members are of interest + * and {@link IContainer#DO_NOT_CHECK_EXISTENCE} if the resource on which the method is + * called should not be checked for existence + * @exception CoreException if this request fails. Reasons include: + *
                + *
              • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource does not exist.
              • + *
              • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource is a project that is not open.
              • + *
              • the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified and + * this resource does not exist.
              • + *
              • The visitor failed with this exception.
              • + *
              + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IContainer#DO_NOT_CHECK_EXISTENCE + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResourceProxyVisitor#visit(IResourceProxy) + * @since 2.1 + */ + public void accept(IResourceProxyVisitor visitor, int memberFlags) throws CoreException; + + /** + * Accepts the given visitor for an optimized traversal. + * The visitor's visit method is called, and is provided with a + * proxy to this resource. The proxy is a transient object that can be queried + * very quickly for information about the resource. If the actual resource + * handle is needed, it can be obtained from the proxy. Requesting the resource + * handle, or the full path of the resource, will degrade performance of the + * visit. + *

              + * The entire subtree under the given resource is traversed to the supplied depth, + * unless the visitor ignores a subtree by returning false from its + * visit method. + *

              + *

              No guarantees are made about the behavior of this method if resources + * are deleted or added during the traversal of this resource hierarchy. If + * resources are deleted during the traversal, they may still be passed to the + * visitor; if resources are created, they may not be passed to the visitor. If + * resources other than the one being visited are modified during the traversal, + * the resource proxy may contain stale information when that resource is + * visited. + *

              + *

              + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified in the member + * flags (recommended), only member resources that exist will be visited. + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is specified, the visit will + * also include any phantom member resource that the workspace is keeping track of. + *

              + *

              + * If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not specified + * (recommended), team private members will not be visited. If the + * {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the member + * flags, team private member resources are visited as well. + *

              + *

              + * If the {@link IContainer#INCLUDE_HIDDEN} flag is not specified (recommended), + * hidden resources will not be visited. If the {@link IContainer#INCLUDE_HIDDEN} flag is specified + * in the member flags, hidden resources are visited as well. + *

              + *

              + * If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified (recommended), + * the resource is checked for existence before the visitor's visit + * method is called. If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is specified + * in the member flags, the resource is not checked for existence before the visitor's + * visit method is called. Children of the resource are never checked + * for existence. + *

              + * + * @param visitor the visitor + * @param depth the depth to which members of this resource should be + * visited. One of {@link IResource#DEPTH_ZERO}, {@link IResource#DEPTH_ONE}, + * or {@link IResource#DEPTH_INFINITE}. + * @param memberFlags bit-wise or of member flag constants + * ({@link IContainer#INCLUDE_PHANTOMS}, {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} + * and {@link IContainer#INCLUDE_HIDDEN}) indicating which members are of interest + * and {@link IContainer#DO_NOT_CHECK_EXISTENCE} if the resource on which the method is + * called should not be checked for existence + * @exception CoreException if this request fails. Reasons include: + *
                + *
              • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource does not exist.
              • + *
              • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource is a project that is not open.
              • + *
              • the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified and + * this resource does not exist.
              • + *
              • The visitor failed with this exception.
              • + *
              + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IContainer#DO_NOT_CHECK_EXISTENCE + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceProxyVisitor#visit(IResourceProxy) + * @since 3.8 + */ + public void accept(IResourceProxyVisitor visitor, int depth, int memberFlags) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource. If the visitor returns true, this method + * visits this resource's members. + *

              + * This is a convenience method, fully equivalent to + * accept(visitor, IResource.DEPTH_INFINITE, IResource.NONE). + *

              + * + * @param visitor the visitor + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • The visitor failed with this exception.
              • + *
              + * @see IResourceVisitor#visit(IResource) + * @see #accept(IResourceVisitor,int,int) + */ + public void accept(IResourceVisitor visitor) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource. If the visitor returns false, + * this resource's members are not visited. + *

              + * The subtree under the given resource is traversed to the supplied depth. + *

              + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   accept(visitor, depth, includePhantoms ? IContainer.INCLUDE_PHANTOMS : IResource.NONE);
              +	 * 
              + *

              + * + * @param visitor the visitor + * @param depth the depth to which members of this resource should be + * visited. One of {@link IResource#DEPTH_ZERO}, {@link IResource#DEPTH_ONE}, + * or {@link IResource#DEPTH_INFINITE}. + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest. + * @exception CoreException if this request fails. Reasons include: + *
                + *
              • includePhantoms is false and + * this resource does not exist.
              • + *
              • includePhantoms is true and + * this resource does not exist and is not a phantom.
              • + *
              • The visitor failed with this exception.
              • + *
              + * @see IResource#isPhantom() + * @see IResourceVisitor#visit(IResource) + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResource#accept(IResourceVisitor,int,int) + */ + public void accept(IResourceVisitor visitor, int depth, boolean includePhantoms) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource. If the visitor returns false, + * this resource's members are not visited. + *

              + * The subtree under the given resource is traversed to the supplied depth. + *

              + *

              + * No guarantees are made about the behavior of this method if resources are + * deleted or added during the traversal of this resource hierarchy. If + * resources are deleted during the traversal, they may still be passed to the + * visitor; if resources are created, they may not be passed to the visitor. + *

              + *

              + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified in the member + * flags (recommended), only member resources that exists are visited. + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is specified, the visit also + * includes any phantom member resource that the workspace is keeping track of. + *

              + *

              + * If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not specified + * (recommended), team private members are not visited. If the + * {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the member + * flags, team private member resources are visited as well. + *

              + *

              + * If the {@link IContainer#EXCLUDE_DERIVED} flag is not specified + * (recommended), derived resources are visited. If the + * {@link IContainer#EXCLUDE_DERIVED} flag is specified in the member + * flags, derived resources are not visited. + *

              + *

              + * If the {@link IContainer#INCLUDE_HIDDEN} flag is not specified (recommended), + * hidden resources will not be visited. If the {@link IContainer#INCLUDE_HIDDEN} flag is specified + * in the member flags, hidden resources are visited as well. + *

              + *

              + * If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified (recommended), + * the resource is checked for existence before the visitor's visit + * method is called. If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is specified + * in the member flags, the resource is not checked for existence before the visitor's + * visit method is called. Children of the resource are never checked + * for existence. + *

              + * + * @param visitor the visitor + * @param depth the depth to which members of this resource should be + * visited. One of {@link IResource#DEPTH_ZERO}, {@link IResource#DEPTH_ONE}, + * or {@link IResource#DEPTH_INFINITE}. + * @param memberFlags bit-wise or of member flag constants + * ({@link IContainer#INCLUDE_PHANTOMS}, {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS}, + * {@link IContainer#INCLUDE_HIDDEN} and {@link IContainer#EXCLUDE_DERIVED}) indicating + * which members are of interest and {@link IContainer#DO_NOT_CHECK_EXISTENCE} + * if the resource on which the method is called should not be checked for existence + * @exception CoreException if this request fails. Reasons include: + *
                + *
              • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource does not exist.
              • + *
              • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource is a project that is not open.
              • + *
              • the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified and + * this resource does not exist.
              • + *
              • The visitor failed with this exception.
              • + *
              + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IContainer#EXCLUDE_DERIVED + * @see IContainer#DO_NOT_CHECK_EXISTENCE + * @see IResource#isDerived() + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResource#isHidden() + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceVisitor#visit(IResource) + * @since 2.0 + */ + public void accept(IResourceVisitor visitor, int depth, int memberFlags) throws CoreException; + + /** + * Removes the local history of this resource and its descendents. + *

              + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + */ + public void clearHistory(IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this resource at the given path. + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   copy(destination, (force ? FORCE : IResource.NONE), monitor);
              +	 * 
              + *

              + *

              + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource copy has been added to its new parent. + *

              + *

              + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource or one of its descendents is not local.
              • + *
              • The source or destination is the workspace root.
              • + *
              • The source is a project but the destination is not.
              • + *
              • The destination is a project but the source is not.
              • + *
              • The resource corresponding to the parent destination path does not exist.
              • + *
              • The resource corresponding to the parent destination path is a closed project.
              • + *
              • A resource at destination path does exist.
              • + *
              • This resource or one of its descendents is out of sync with the local file + * system and force is false.
              • + *
              • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
              • + *
              • The source resource is a file and the destination path specifies a project.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + */ + public void copy(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this resource at the given path. The resource's + * descendents are copied as well. The path of this resource must not be a + * prefix of the destination path. The workspace root may not be the source or + * destination location of a copy operation, and a project can only be copied to + * another project. After successful completion, corresponding new resources + * will exist at the given path; their contents and properties will be copies of + * the originals. The original resources are not affected. + *

              + * The supplied path may be absolute or relative. Absolute paths fully specify + * the new location for the resource, including its project. Relative paths are + * considered to be relative to the container of the resource being copied. A + * trailing separator is ignored. + *

              + *

              + * Calling this method with a one segment absolute destination path is + * equivalent to calling: + *

              +	 *   copy(workspace.newProjectDescription(folder.getName()),updateFlags,monitor);
              +	 * 
              + *

              + *

              When a resource is copied, its persistent properties are copied with it. + * Session properties and markers are not copied. + *

              + *

              + * The FORCE update flag controls how this method deals with cases + * where the workspace is not completely in sync with the local file system. If + * FORCE is not specified, the method will only attempt to copy + * resources that are in sync with the corresponding files and directories in + * the local file system; it will fail if it encounters a resource that is out + * of sync with the file system. However, if FORCE is specified, + * the method copies all corresponding files and directories from the local file + * system, including ones that have been recently updated or created. Note that + * in both settings of the FORCE flag, the operation fails if the + * newly created resources in the workspace would be out of sync with the local + * file system; this ensures files in the file system cannot be accidentally + * overwritten. + *

              + *

              + * The SHALLOW update flag controls how this method deals with linked + * resources. If SHALLOW is not specified, then the underlying + * contents of the linked resource will always be copied in the file system. In + * this case, the destination of the copy will never be a linked resource or + * contain any linked resources. If SHALLOW is specified when a + * linked resource is copied into another project, a new linked resource is + * created in the destination project that points to the same file system + * location. When a project containing linked resources is copied, the new + * project will contain the same linked resources pointing to the same file + * system locations. For both of these shallow cases, no files on disk under + * the linked resource are actually copied. With the SHALLOW flag, + * copying of linked resources into anything other than a project is not + * permitted. The SHALLOW update flag is ignored when copying non- + * linked resources. + *

              + *

              + * The {@link #DERIVED} update flag indicates that the new resource + * should immediately be set as a derived resource. Specifying this flag + * is equivalent to atomically calling {@link #setDerived(boolean)} + * with a value of true immediately after creating the resource. + *

              + *

              + * The {@link #TEAM_PRIVATE} update flag indicates that the new resource + * should immediately be set as a team private resource. Specifying this flag + * is equivalent to atomically calling {@link #setTeamPrivateMember(boolean)} + * with a value of true immediately after creating the resource. + *

              + *

              + * The {@link #HIDDEN} update flag indicates that the new resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link #setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

              + *

              + * Update flags other than those listed above are ignored. + *

              + *

              + * This operation changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resource copy has been added to its new parent. + *

              + *

              + * An attempt will be made to copy the local history for this resource and its children, + * to the destination. Since local history existence is a safety-net mechanism, failure + * of this action will not result in automatic failure of the copy operation. + *

              + *

              + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param destination the destination path + * @param updateFlags bit-wise or of update flag constants + * ({@link #FORCE}, {@link #SHALLOW}, {@link #DERIVED}, {@link #TEAM_PRIVATE}, {@link #HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource or one of its descendents is not local.
              • + *
              • The source or destination is the workspace root.
              • + *
              • The source is a project but the destination is not.
              • + *
              • The destination is a project but the source is not.
              • + *
              • The resource corresponding to the parent destination path does not exist.
              • + *
              • The resource corresponding to the parent destination path is a closed project.
              • + *
              • The source is a linked resource, but the destination is not a project, + * and SHALLOW is specified.
              • + *
              • A resource at destination path does exist.
              • + *
              • This resource or one of its descendants is out of sync with the local file + * system and FORCE is not specified.
              • + *
              • The workspace and the local file system are out of sync + * at the destination resource or one of its descendants.
              • + *
              • The source resource is a file and the destination path specifies a project.
              • + *
              • The source is a linked resource, and the destination path does not + * specify a project.
              • + *
              • The location of the source resource on disk is the same or a prefix of + * the location of the destination resource on disk.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see #FORCE + * @see #SHALLOW + * @see #DERIVED + * @see #TEAM_PRIVATE + * @see IResourceRuleFactory#copyRule(IResource, IResource) + * @since 2.0 + */ + public void copy(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this project using the given project description. + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   copy(description, (force ? FORCE : IResource.NONE), monitor);
              +	 * 
              + *

              + *

              + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource copy has been added to its new parent. + *

              + *

              + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param description the destination project description + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource or one of its descendents is not local.
              • + *
              • This resource is not a project.
              • + *
              • The project described by the given description already exists.
              • + *
              • This resource or one of its descendents is out of sync with the local file + * system and force is false.
              • + *
              • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + */ + public void copy(IProjectDescription description, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this project using the given project description. + * The project's descendents are copied as well. The description specifies the + * name, location and attributes of the new project. After successful + * completion, corresponding new resources will exist at the given path; their + * contents and properties will be copies of the originals. The original + * resources are not affected. + *

              + * When a resource is copied, its persistent properties are copied with it. + * Session properties and markers are not copied. + *

              + *

              The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to copy resources that are in sync with the corresponding files and + * directories in the local file system; it will fail if it encounters a + * resource that is out of sync with the file system. However, if + * FORCE is specified, the method copies all corresponding files + * and directories from the local file system, including ones that have been + * recently updated or created. Note that in both settings of the + * FORCE flag, the operation fails if the newly created resources + * in the workspace would be out of sync with the local file system; this + * ensures files in the file system cannot be accidentally overwritten. + *

              + *

              + * The SHALLOW update flag controls how this method deals with + * linked resources. If SHALLOW is not specified, then the + * underlying contents of any linked resources in the project will always be + * copied in the file system. In this case, the destination of the copy will + * never contain any linked resources. If SHALLOW is specified + * when a project containing linked resources is copied, new linked resources + * are created in the destination project that point to the same file system + * locations. In this case, no files on disk under linked resources are + * actually copied. The SHALLOW update flag is ignored when copying + * non- linked resources. + *

              + *

              + * Update flags other than FORCE or SHALLOW are ignored. + *

              + *

              + * An attempt will be made to copy the local history for this resource and its children, + * to the destination. Since local history existence is a safety-net mechanism, failure + * of this action will not result in automatic failure of the copy operation. + *

              + *

              This operation changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resource copy has been added to its new parent. + *

              + *

              + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param description the destination project description + * @param updateFlags bit-wise or of update flag constants + * (FORCE and SHALLOW) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource or one of its descendents is not local.
              • + *
              • This resource is not a project.
              • + *
              • The project described by the given description already exists.
              • + *
              • This resource or one of its descendents is out of sync with the local file + * system and FORCE is not specified.
              • + *
              • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #FORCE + * @see #SHALLOW + * @see IResourceRuleFactory#copyRule(IResource, IResource) + * @since 2.0 + */ + public void copy(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates and returns the marker with the specified type on this resource. + * Marker type ids should be the id of an extension installed in the + * org.eclipse.core.resources.markers extension + * point. The specified type string must not be null. + * + * @param type the type of the marker to create + * @return the handle of the new marker + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is a project that is not open.
              • + *
              + * @see IResourceRuleFactory#markerRule(IResource) + */ + public IMarker createMarker(String type) throws CoreException; + + /** + * Creates a resource proxy representing the current state of this resource. + *

              + * Note that once a proxy has been created, it does not stay in sync + * with the corresponding resource. Changes to the resource after + * the proxy is created will not be reflected in the state of the proxy. + *

              + * + * @return A proxy representing this resource + * @since 3.2 + */ + public IResourceProxy createProxy(); + + /** + * Deletes this resource from the workspace. + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   delete(force ? FORCE : IResource.NONE, monitor);
              +	 * 
              + *

              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource could not be deleted for some reason.
              • + *
              • This resource or one of its descendents is out of sync with the local file system + * and force is false.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IResource#delete(int,IProgressMonitor) + */ + public void delete(boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this resource from the workspace. + * Deletion applies recursively to all members of this resource in a "best- + * effort" fashion. That is, all resources which can be deleted are deleted. + * Resources which could not be deleted are noted in a thrown exception. The + * method does not fail if resources do not exist; it fails only if resources + * could not be deleted. + *

              + * Deleting a non-linked resource also deletes its contents from the local file + * system. In the case of a file or folder resource, the corresponding file or + * directory in the local file system is deleted. Deleting an open project + * recursively deletes its members; deleting a closed project just gets rid of + * the project itself (closed projects have no members); files in the project's + * local content area are retained; referenced projects are unaffected. + *

              + *

              + * Deleting a linked resource does not delete its contents from the file system, + * it just removes that resource and its children from the workspace. Deleting + * children of linked resources does remove the contents from the file system. + *

              + *

              + * Deleting a resource also deletes its session and persistent properties and + * markers. + *

              + *

              + * Deleting a non-project resource which has sync information converts the + * resource to a phantom and retains the sync information for future use. + *

              + *

              + * Deleting the workspace root resource recursively deletes all projects, + * and removes all markers, properties, sync info and other data related to the + * workspace root; the root resource itself is not deleted, however. + *

              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + *

              + * The {@link #FORCE} update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local + * file system. If {@link #FORCE} is not specified, the method will only + * attempt to delete files and directories in the local file system that + * correspond to, and are in sync with, resources in the workspace; it will fail + * if it encounters a file or directory in the file system that is out of sync + * with the workspace. This option ensures there is no unintended data loss; + * it is the recommended setting. However, if {@link #FORCE} is specified, + * the method will ruthlessly attempt to delete corresponding files and + * directories in the local file system, including ones that have been recently + * updated or created. + *

              + *

              + * The {@link #KEEP_HISTORY} update flag controls whether or not files that + * are about to be deleted from the local file system have their current + * contents saved in the workspace's local history. The local history mechanism + * serves as a safety net to help the user recover from mistakes that might + * otherwise result in data loss. Specifying {@link #KEEP_HISTORY} is + * recommended except in circumstances where past states of the files are of no + * conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. Hence {@link #KEEP_HISTORY} is only really applicable + * when deleting files and folders, but not projects. + *

              + *

              + * The {@link #ALWAYS_DELETE_PROJECT_CONTENT} update flag controls how + * project deletions are handled. If {@link #ALWAYS_DELETE_PROJECT_CONTENT} + * is specified, then the files and folders in a project's local content area + * are deleted, regardless of whether the project is open or closed; + * {@link #FORCE} is assumed regardless of whether it is specified. If + * {@link #NEVER_DELETE_PROJECT_CONTENT} is specified, then the files and + * folders in a project's local content area are retained, regardless of whether + * the project is open or closed; the {@link #FORCE} flag is ignored. If + * neither of these flags is specified, files and folders in a project's local + * content area from open projects (subject to the {@link #FORCE} flag), but + * never from closed projects. + *

              + * + * @param updateFlags bit-wise or of update flag constants ( + * {@link #FORCE}, {@link #KEEP_HISTORY}, + * {@link #ALWAYS_DELETE_PROJECT_CONTENT}, + * and {@link #NEVER_DELETE_PROJECT_CONTENT}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource could not be deleted for some reason.
              • + *
              • This resource or one of its descendents is out of sync with the local file system + * and {@link #FORCE} is not specified.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IFile#delete(boolean, boolean, IProgressMonitor) + * @see IFolder#delete(boolean, boolean, IProgressMonitor) + * @see #FORCE + * @see #KEEP_HISTORY + * @see #ALWAYS_DELETE_PROJECT_CONTENT + * @see #NEVER_DELETE_PROJECT_CONTENT + * @see IResourceRuleFactory#deleteRule(IResource) + * @since 2.0 + */ + public void delete(int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes all markers on this resource of the given type, and, + * optionally, deletes such markers from its children. If includeSubtypes + * is false, only markers whose type exactly matches + * the given type are deleted. + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

              + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param depth how far to recurse (see IResource.DEPTH_* ) + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is a project that is not open.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException; + + /** + * Compares two objects for equality; + * for resources, equality is defined in terms of their handles: + * same resource type, equal full paths, and identical workspaces. + * Resources are not equal to objects other than resources. + * + * @param other the other object + * @return an indication of whether the objects are equals + * @see #getType() + * @see #getFullPath() + * @see #getWorkspace() + */ + @Override + public boolean equals(Object other); + + /** + * Returns whether this resource exists in the workspace. + *

              + * IResource objects are lightweight handle objects + * used to access resources in the workspace. However, having a + * handle object does not necessarily mean the workspace really + * has such a resource. When the workspace does have a genuine + * resource of a matching type, the resource is said to + * exist, and this method returns true; + * in all other cases, this method returns false. + * In particular, it returns false if the workspace + * has no resource at that path, or if it has a resource at that + * path with a type different from the type of this resource handle. + *

              + *

              + * Note that no resources ever exist under a project + * that is closed; opening a project may bring some + * resources into existence. + *

              + *

              + * The name and path of a resource handle may be invalid. + * However, validation checks are done automatically as a + * resource is created; this means that any resource that exists + * can be safely assumed to have a valid name and path. + *

              + * + * @return true if the resource exists, otherwise + * false + */ + public boolean exists(); + + /** + * Returns the marker with the specified id on this resource, + * Returns null if there is no matching marker. + * + * @param id the id of the marker to find + * @return a marker or null + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is a project that is not open.
              • + *
              + */ + public IMarker findMarker(long id) throws CoreException; + + /** + * Returns all markers of the specified type on this resource, + * and, optionally, on its children. If includeSubtypes + * is false, only markers whose type exactly matches + * the given type are returned. Returns an empty array if there + * are no matching markers. + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param depth how far to recurse (see IResource.DEPTH_* ) + * @return an array of markers + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is a project that is not open.
              • + *
              + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + */ + public IMarker[] findMarkers(String type, boolean includeSubtypes, int depth) throws CoreException; + + /** + * Returns the maximum value of the {@link IMarker#SEVERITY} attribute across markers + * of the specified type on this resource, and, optionally, on its children. + * If includeSubtypesis false, only markers whose type + * exactly matches the given type are considered. + * Returns -1 if there are no matching markers. + * Returns {@link IMarker#SEVERITY_ERROR} if any of the markers has a severity + * greater than or equal to {@link IMarker#SEVERITY_ERROR}. + * + * @param type the type of marker to consider (normally {@link IMarker#PROBLEM} + * or one of its subtypes), or null to indicate all types + * + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param depth how far to recurse (see IResource.DEPTH_* ) + * @return {@link IMarker#SEVERITY_INFO}, {@link IMarker#SEVERITY_WARNING}, {@link IMarker#SEVERITY_ERROR}, or -1 + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is a project that is not open.
              • + *
              + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @since 3.3 + */ + public int findMaxProblemSeverity(String type, boolean includeSubtypes, int depth) throws CoreException; + + /** + * Returns the file extension portion of this resource's name, + * or null if it does not have one. + *

              + * The file extension portion is defined as the string + * following the last period (".") character in the name. + * If there is no period in the name, the path has no + * file extension portion. If the name ends in a period, + * the file extension portion is the empty string. + *

              + *

              + * This is a resource handle operation; the resource need + * not exist. + *

              + * + * @return a string file extension + * @see #getName() + */ + public String getFileExtension(); + + /** + * Returns the full, absolute path of this resource relative to the + * workspace. + *

              + * This is a resource handle operation; the resource need + * not exist. + * If this resource does exist, its path can be safely assumed to be valid. + *

              + *

              + * A resource's full path indicates the route from the root of the workspace + * to the resource. Within a workspace, there is exactly one such path + * for any given resource. The first segment of these paths name a project; + * remaining segments, folders and/or files within that project. + * The returned path never has a trailing separator. The path of the + * workspace root is Path.ROOT. + *

              + *

              + * Since absolute paths contain the name of the project, they are + * vulnerable when the project is renamed. For most situations, + * project-relative paths are recommended over absolute paths. + *

              + * + * @return the absolute path of this resource + * @see #getProjectRelativePath() + * @see Path#ROOT + */ + public IPath getFullPath(); + + /** + * Returns a cached value of the local time stamp on disk for this resource, or + * NULL_STAMP if the resource does not exist or is not local or is + * not accessible. The return value is represented as the number of milliseconds + * since the epoch (00:00:00 GMT, January 1, 1970). + * The returned value may not be the same as the actual time stamp + * on disk if the file has been modified externally since the last local refresh. + *

              + * Note that due to varying file system timing granularities, this value is not guaranteed + * to change every time the file is modified. For a more reliable indication of whether + * the file has changed, use getModificationStamp. + * + * @return a local file system time stamp, or NULL_STAMP. + * @since 3.0 + */ + public long getLocalTimeStamp(); + + /** + * Returns the absolute path in the local file system to this resource, + * or null if no path can be determined. + *

              + * If this resource is the workspace root, this method returns + * the absolute local file system path of the platform working area. + *

              + * If this resource is a project that exists in the workspace, this method + * returns the path to the project's local content area. This is true regardless + * of whether the project is open or closed. This value will be null in the case + * where the location is relative to an undefined workspace path variable. + *

              + * If this resource is a linked resource under a project that is open, this + * method returns the resolved path to the linked resource's local contents. + * This value will be null in the case where the location is relative to an + * undefined workspace path variable. + *

              + * If this resource is a file or folder under a project that exists, or a + * linked resource under a closed project, this method returns a (non- + * null) path computed from the location of the project's local + * content area and the project- relative path of the file or folder. This is + * true regardless of whether the file or folders exists, or whether the project + * is open or closed. In the case of linked resources, the location of a linked resource + * within a closed project is too computed from the location of the + * project's local content area and the project-relative path of the resource. If the + * linked resource resides in an open project then its location is computed + * according to the link. + *

              + * If this resource is a project that does not exist in the workspace, + * or a file or folder below such a project, this method returns + * null. This method also returns null if called + * on a resource that is not stored in the local file system. For such resources + * {@link #getLocationURI()} should be used instead. + *

              + * + * @return the absolute path of this resource in the local file system, + * or null if no path can be determined + * @see #getRawLocation() + * @see #getLocationURI() + * @see IProjectDescription#setLocation(IPath) + * @see Platform#getLocation() + */ + public IPath getLocation(); + + /** + * Returns the absolute URI of this resource, + * or null if no URI can be determined. + *

              + * If this resource is the workspace root, this method returns + * the absolute location of the platform working area. + *

              + * If this resource is a project that exists in the workspace, this method + * returns the URI to the project's local content area. This is true regardless + * of whether the project is open or closed. This value will be null in the case + * where the location is relative to an undefined workspace path variable. + *

              + * If this resource is a linked resource under a project that is open, this + * method returns the resolved URI to the linked resource's local contents. + * This value will be null in the case where the location is relative to an + * undefined workspace path variable. + *

              + * If this resource is a file or folder under a project that exists, or a + * linked resource under a closed project, this method returns a (non- + * null) URI computed from the location of the project's local + * content area and the project- relative path of the file or folder. This is + * true regardless of whether the file or folders exists, or whether the project + * is open or closed. In the case of linked resources, the location of a linked resource + * within a closed project is computed from the location of the + * project's local content area and the project-relative path of the resource. If the + * linked resource resides in an open project then its location is computed + * according to the link. + *

              + * If this resource is a project that does not exist in the workspace, + * or a file or folder below such a project, this method returns + * null. + *

              + * + * @return the absolute URI of this resource, + * or null if no URI can be determined + * @see #getRawLocation() + * @see IProjectDescription#setLocation(IPath) + * @see Platform#getLocation() + * @see java.net.URI + * @since 3.2 + */ + public URI getLocationURI(); + + /** + * Returns a marker handle with the given id on this resource. + * This resource is not checked to see if it has such a marker. + * The returned marker need not exist. + * This resource need not exist. + * + * @param id the id of the marker + * @return the specified marker handle + * @see IMarker#getId() + */ + public IMarker getMarker(long id); + + /** + * Returns a non-negative modification stamp, or NULL_STAMP if + * the resource does not exist or is not local or is not accessible. + *

              + * A resource's modification stamp gets updated each time a resource is modified. + * If a resource's modification stamp is the same, the resource has not changed. + * Conversely, if a resource's modification stamp is different, some aspect of it + * (other than properties) has been modified at least once (possibly several times). + * Resource modification stamps are preserved across project close/re-open, + * and across workspace shutdown/restart. + * The magnitude or sign of the numerical difference between two modification stamps + * is not significant. + *

              + *

              + * The following things affect a resource's modification stamp: + *

                + *
              • creating a non-project resource (changes from NULL_STAMP)
              • + *
              • changing the contents of a file
              • + *
              • touching a resource
              • + *
              • setting the attributes of a project presented in a project description
              • + *
              • deleting a resource (changes to NULL_STAMP)
              • + *
              • moving a resource (source changes to NULL_STAMP, + destination changes from NULL_STAMP)
              • + *
              • copying a resource (destination changes from NULL_STAMP)
              • + *
              • making a resource local
              • + *
              • closing a project (changes to NULL_STAMP)
              • + *
              • opening a project (changes from NULL_STAMP)
              • + *
              • adding or removing a project nature (changes from NULL_STAMP)
              • + *
              + * The following things do not affect a resource's modification stamp: + *
                + *
              • "reading" a resource
              • + *
              • adding or removing a member of a project or folder
              • + *
              • setting a session property
              • + *
              • setting a persistent property
              • + *
              • saving the workspace
              • + *
              • shutting down and re-opening a workspace
              • + *
              + *

              + * + * @return the modification stamp, or NULL_STAMP if this resource either does + * not exist or exists as a closed project + * @see IResource#NULL_STAMP + * @see #revertModificationStamp(long) + */ + public long getModificationStamp(); + + /** + * Returns the name of this resource. + * The name of a resource is synonymous with the last segment + * of its full (or project-relative) path for all resources other than the + * workspace root. The workspace root's name is the empty string. + *

              + * This is a resource handle operation; the resource need + * not exist. + *

              + *

              + * If this resource exists, its name can be safely assumed to be valid. + *

              + * + * @return the name of the resource + * @see #getFullPath() + * @see #getProjectRelativePath() + */ + public String getName(); + + /** + * Returns the path variable manager for this resource. + * + * @return the path variable manager + * @see IPathVariableManager + * @since 3.6 + */ + public IPathVariableManager getPathVariableManager(); + + /** + * Returns the resource which is the parent of this resource, + * or null if it has no parent (that is, this + * resource is the workspace root). + *

              + * The full path of the parent resource is the same as this + * resource's full path with the last segment removed. + *

              + *

              + * This is a resource handle operation; neither the resource + * nor the resulting resource need exist. + *

              + * + * @return the parent resource of this resource, + * or null if it has no parent + */ + public IContainer getParent(); + + /** + * Returns a copy of the map of this resource's persistent properties. + * Returns an empty map if this resource has no persistent properties. + * + * @return the map containing the persistent properties where the key is + * the {@link QualifiedName} of the property and the value is the {@link String} + * value of the property. + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is not local.
              • + *
              • This resource is a project that is not open.
              • + *
              + * @see #setPersistentProperty(QualifiedName, String) + * @since 3.4 + */ + public Map getPersistentProperties() throws CoreException; + + /** + * Returns the value of the persistent property of this resource identified + * by the given key, or null if this resource has no such property. + * + * @param key the qualified name of the property + * @return the string value of the property, + * or null if this resource has no such property + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is not local.
              • + *
              • This resource is a project that is not open.
              • + *
              + * @see #setPersistentProperty(QualifiedName, String) + */ + public String getPersistentProperty(QualifiedName key) throws CoreException; + + /** + * Returns the project which contains this resource. + * Returns itself for projects and null + * for the workspace root. + *

              + * A resource's project is the one named by the first segment + * of its full path. + *

              + *

              + * This is a resource handle operation; neither the resource + * nor the resulting project need exist. + *

              + * + * @return the project handle + */ + public IProject getProject(); + + /** + * Returns a relative path of this resource with respect to its project. + * Returns the empty path for projects and the workspace root. + *

              + * This is a resource handle operation; the resource need not exist. + * If this resource does exist, its path can be safely assumed to be valid. + *

              + *

              + * A resource's project-relative path indicates the route from the project + * to the resource. Within a project, there is exactly one such path + * for any given resource. The returned path never has a trailing slash. + *

              + *

              + * Project-relative paths are recommended over absolute paths, since + * the former are not affected if the project is renamed. + *

              + * + * @return the relative path of this resource with respect to its project + * @see #getFullPath() + * @see #getProject() + * @see Path#EMPTY + */ + public IPath getProjectRelativePath(); + + /** + * Returns the file system location of this resource, or null if no + * path can be determined. The returned path will either be an absolute file + * system path, or a relative path whose first segment is the name of a + * workspace path variable. + *

              + * If this resource is an existing project, the returned path will be equal to + * the location path in the project description. If this resource is a linked + * resource in an open project, the returned path will be equal to the location + * path supplied when the linked resource was created. In all other cases, this + * method returns the same value as {@link #getLocation()}. + *

              + * + * @return the raw path of this resource in the local file system, or + * null if no path can be determined + * @see #getLocation() + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @see IPathVariableManager + * @see IProjectDescription#getLocation() + * @since 2.1 + */ + public IPath getRawLocation(); + + /** + * Returns the raw location of this resource, or null if no + * path can be determined. The returned path will either be an absolute URI, + * or a relative URI whose first path segment is the name of a workspace path variable. + * Since the returned location may contain unresolved variables, the resulting URI + * is typically only suitable for display. To access or manipulate the actual resource + * backing location, clients should obtain the resolved location using {@link #getLocationURI()}. + *

              + * If this resource is an existing project, the returned location will be equal to + * the location URI in the project description. If this resource is a linked + * resource in an open project, the returned location will be equal to the location URI + * supplied when the linked resource was created. In all other cases, this + * method returns the same value as {@link #getLocationURI()}. + *

              + * + * @return the raw location of this resource, or null if no + * location can be determined + * @see #getLocationURI() + * @see IFile#createLink(URI, int, IProgressMonitor) + * @see IFolder#createLink(URI, int, IProgressMonitor) + * @see IPathVariableManager + * @see IProjectDescription#getLocationURI() + * @since 3.2 + */ + public URI getRawLocationURI(); + + /** + * Gets this resource's extended attributes from the file system, + * or null if the attributes could not be obtained. + *

              + * Reasons for a null return value include: + *

                + *
              • This resource does not exist.
              • + *
              • This resource is not local.
              • + *
              • This resource is a project that is not open.
              • + *
              + *

              + * Attributes that are not supported by the underlying file system + * will have a value of false. + *

              + * Sample usage:
              + *
              + * + * IResource resource;
              + * ...
              + * ResourceAttributes attributes = resource.getResourceAttributes();
              + * if (attributes != null) { + * attributes.setExecutable(true);
              + * resource.setResourceAttributes(attributes);
              + * } + *
              + *

              + * + * @return the extended attributes from the file system, or + * null if they could not be obtained + * @see #setResourceAttributes(ResourceAttributes) + * @see ResourceAttributes + * @since 3.1 + */ + public ResourceAttributes getResourceAttributes(); + + /** + * Returns a copy of the map of this resource's session properties. + * Returns an empty map if this resource has no session properties. + * + * @return the map containing the session properties where the key is + * the {@link QualifiedName} of the property and the value is the property + * value (an {@link Object}. + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is not local.
              • + *
              • This resource is a project that is not open.
              • + *
              + * @see #setSessionProperty(QualifiedName, Object) + * @since 3.4 + */ + public Map getSessionProperties() throws CoreException; + + /** + * Returns the value of the session property of this resource identified + * by the given key, or null if this resource has no such property. + * + * @param key the qualified name of the property + * @return the value of the session property, + * or null if this resource has no such property + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is not local.
              • + *
              • This resource is a project that is not open.
              • + *
              + * @see #setSessionProperty(QualifiedName, Object) + */ + public Object getSessionProperty(QualifiedName key) throws CoreException; + + /** + * Returns the type of this resource. + * The returned value will be one of FILE, + * FOLDER, PROJECT, ROOT. + *

              + *

                + *
              • All resources of type FILE implement IFile.
              • + *
              • All resources of type FOLDER implement IFolder.
              • + *
              • All resources of type PROJECT implement IProject.
              • + *
              • All resources of type ROOT implement IWorkspaceRoot.
              • + *
              + *

              + *

              + * This is a resource handle operation; the resource need + * not exist in the workspace. + *

              + * + * @return the type of this resource + * @see #FILE + * @see #FOLDER + * @see #PROJECT + * @see #ROOT + */ + public int getType(); + + /** + * Returns the workspace which manages this resource. + *

              + * This is a resource handle operation; the resource need + * not exist in the workspace. + *

              + * + * @return the workspace + */ + public IWorkspace getWorkspace(); + + /** + * Returns whether this resource is accessible. For files and folders, + * this is equivalent to existing; for projects, + * this is equivalent to existing and being open. The workspace root + * is always accessible. + * + * @return true if this resource is accessible, and + * false otherwise + * @see #exists() + * @see IProject#isOpen() + */ + public boolean isAccessible(); + + /** + * Returns whether this resource subtree is marked as derived. Returns + * false if this resource does not exist. + * + *

              + * This is a convenience method, + * fully equivalent to isDerived(IResource.NONE). + *

              + * + * @return true if this resource is marked as derived, and + * false otherwise + * @see #setDerived(boolean) + * @since 2.0 + */ + public boolean isDerived(); + + /** + * Returns whether this resource subtree is marked as derived. Returns + * false if this resource does not exist. + * + *

              + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true, if this resource, or any parent resource, is marked + * as derived. If the {@link #CHECK_ANCESTORS} option flag is not specified, + * this method returns false for children of derived resources. + *

              + * + * @param options bit-wise or of option flag constants + * (only {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource subtree is derived, and + * false otherwise + * @see IResource#setDerived(boolean) + * @since 3.4 + */ + public boolean isDerived(int options); + + /** + * Returns whether this resource is hidden in the resource tree. Returns + * false if this resource does not exist. + *

              + * This operation is not related to the file system hidden attribute accessible using + * {@link ResourceAttributes#isHidden()}. + *

              + * + * @return true if this resource is hidden , and + * false otherwise + * @see #setHidden(boolean) + * @since 3.4 + */ + public boolean isHidden(); + + /** + * Returns whether this resource is hidden in the resource tree. Returns + * false if this resource does not exist. + *

              + * This operation is not related to the file system hidden attribute + * accessible using {@link ResourceAttributes#isHidden()}. + *

              + *

              + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true if this resource, or any parent resource, is a hidden + * resource. If the {@link #CHECK_ANCESTORS} option flag is not specified, + * this method returns false for children of hidden resources. + *

              + * + * @param options + * bit-wise or of option flag constants (only + * {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource is hidden , and + * false otherwise + * @see #setHidden(boolean) + * @since 3.5 + */ + public boolean isHidden(int options); + + /** + * Returns whether this resource has been linked to + * a location other than the default location calculated by the platform. + *

              + * This is a convenience method, fully equivalent to + * isLinked(IResource.NONE). + *

              + * + * @return true if this resource is linked, and + * false otherwise + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @since 2.1 + */ + public boolean isLinked(); + + /** + * Returns whether this resource is a virtual resource. Returns true + * for folders that have been marked virtual using the {@link #VIRTUAL} update + * flag. Returns false in all other cases, including + * the case where this resource does not exist. The workspace root, projects + * and files currently cannot be made virtual. + * + * @return true if this resource is virtual, and + * false otherwise + * @see IFile#create(java.io.InputStream, int, IProgressMonitor) + * @see #VIRTUAL + * @since 3.6 + */ + public boolean isVirtual(); + + /** + * Returns true if this resource has been linked to + * a location other than the default location calculated by the platform. This + * location can be outside the project's content area or another location + * within the project. Returns false in all other cases, including + * the case where this resource does not exist. The workspace root and + * projects are never linked. + *

              + * This method returns true for a resource that has been linked using + * the createLink method. + *

              + *

              + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true if this resource, or any parent resource, is a linked + * resource. If the {@link #CHECK_ANCESTORS} option flag is not specified, + * this method returns false for children of linked resources. + *

              + * + * @param options bit-wise or of option flag constants + * (only {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource is linked, and + * false otherwise + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @since 3.2 + */ + public boolean isLinked(int options); + + /** + * Returns whether this resource and its members (to the + * specified depth) are expected to have their contents (and properties) + * available locally. Returns false in all other cases, + * including the case where this resource does not exist. The workspace + * root and projects are always local. + *

              + * When a resource is not local, its content and properties are + * unavailable for both reading and writing. + *

              + * + * @param depth valid values are DEPTH_ZERO, + * DEPTH_ONE, or DEPTH_INFINITE + * @return true if this resource is local, and + * false otherwise + * + * @see #setLocal(boolean, int, IProgressMonitor) + * @deprecated This API is no longer in use. Note that this API is unrelated + * to whether the resource is in the local file system versus some other file system. + */ + @Deprecated + public boolean isLocal(int depth); + + /** + * Returns whether this resource is a phantom resource. + *

              + * The workspace uses phantom resources to remember outgoing deletions and + * incoming additions relative to an external synchronization partner. Phantoms + * appear and disappear automatically as a byproduct of synchronization. + * Since the workspace root cannot be synchronized in this way, it is never a phantom. + * Projects are also never phantoms. + *

              + *

              + * The key point is that phantom resources do not exist (in the technical + * sense of exists, which returns false + * for phantoms) are therefore invisible except through a handful of + * phantom-enabled API methods (notably IContainer.members(boolean)). + *

              + * + * @return true if this resource is a phantom resource, and + * false otherwise + * @see #exists() + * @see IContainer#members(boolean) + * @see IContainer#findMember(String, boolean) + * @see IContainer#findMember(IPath, boolean) + * @see ISynchronizer + */ + public boolean isPhantom(); + + /** + * Returns whether this resource is marked as read-only in the file system. + * + * @return true if this resource is read-only, + * false otherwise + * @deprecated use {@link #getResourceAttributes()} + */ + @Deprecated + public boolean isReadOnly(); + + /** + * Returns whether this resource and its descendents to the given depth + * are considered to be in sync with the local file system. + *

              + * A resource is considered to be in sync if all of the following + * conditions are true: + *

                + *
              • The resource exists in both the workspace and the file system.
              • + *
              • The timestamp in the file system has not changed since the + * last synchronization.
              • + *
              • The resource in the workspace is of the same type as the corresponding + * file in the file system (they are either both files or both folders).
              • + *
              + * A resource is also considered to be in sync if it is missing from both + * the workspace and the file system. In all other cases the resource is + * considered to be out of sync. + *

              + *

              + * This operation interrogates files and folders in the local file system; + * depending on the speed of the local file system and the requested depth, + * this operation may be time-consuming. + *

              + * + * @param depth the depth (one of IResource.DEPTH_ZERO, + * DEPTH_ONE, or DEPTH_INFINITE) + * @return true if this resource and its descendents to the + * specified depth are synchronized, and false in all other + * cases + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see #refreshLocal(int, IProgressMonitor) + * @since 2.0 + */ + public boolean isSynchronized(int depth); + + /** + * Returns whether this resource is a team private member of its parent container. + * Returns false if this resource does not exist. + * + * @return true if this resource is a team private member, and + * false otherwise + * @see #setTeamPrivateMember(boolean) + * @since 2.0 + */ + public boolean isTeamPrivateMember(); + + /** + * Returns whether this resource is a team private member of its parent + * container. Returns false if this resource does not exist. + *

              + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true if this resource, or any parent resource, is a team + * private member. If the {@link #CHECK_ANCESTORS} option flag is not + * specified, this method returns false for children of team private + * members. + *

              + * + * @param options + * bit-wise or of option flag constants (only + * {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource is a team private member, and + * false otherwise + * @see #setTeamPrivateMember(boolean) + * @since 3.5 + */ + public boolean isTeamPrivateMember(int options); + + /** + * Moves this resource so that it is located at the given path. + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   move(destination, force ? FORCE : IResource.NONE, monitor);
              +	 * 
              + *

              + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource has been removed from its parent + * and that a corresponding resource has been added to its new parent. + * Additional information provided with resource delta shows that these + * additions and removals are related. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource or one of its descendents is not local.
              • + *
              • The source or destination is the workspace root.
              • + *
              • The source is a project but the destination is not.
              • + *
              • The destination is a project but the source is not.
              • + *
              • The resource corresponding to the parent destination path does not exist.
              • + *
              • The resource corresponding to the parent destination path is a closed + * project.
              • + *
              • A resource at destination path does exist.
              • + *
              • A resource of a different type exists at the destination path.
              • + *
              • This resource or one of its descendents is out of sync with the local file + * system and force is false.
              • + *
              • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              • The source resource is a file and the destination path specifies a project.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + */ + public void move(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Moves this resource so that it is located at the given path. + * The path of the resource must not be a prefix of the destination path. The + * workspace root may not be the source or destination location of a move + * operation, and a project can only be moved to another project. After + * successful completion, the resource and any direct or indirect members will + * no longer exist; but corresponding new resources will now exist at the given + * path. + *

              + * The supplied path may be absolute or relative. Absolute paths fully specify + * the new location for the resource, including its project. Relative paths are + * considered to be relative to the container of the resource being moved. A + * trailing slash is ignored. + *

              + *

              + * Calling this method with a one segment absolute destination path is + * equivalent to calling: + *

              +	 IProjectDescription description = getDescription();
              +	 description.setName(path.lastSegment());
              +	 move(description, updateFlags, monitor);
              +	 * 
              + *

              + *

              When a resource moves, its session and persistent properties move with + * it. Likewise for all other attributes of the resource including markers. + *

              + *

              + * The FORCE update flag controls how this method deals with cases + * where the workspace is not completely in sync with the local file system. If + * FORCE is not specified, the method will only attempt to move + * resources that are in sync with the corresponding files and directories in + * the local file system; it will fail if it encounters a resource that is out + * of sync with the file system. However, if FORCE is specified, + * the method moves all corresponding files and directories from the local file + * system, including ones that have been recently updated or created. Note that + * in both settings of the FORCE flag, the operation fails if the + * newly created resources in the workspace would be out of sync with the local + * file system; this ensures files in the file system cannot be accidentally + * overwritten. + *

              + *

              + * The KEEP_HISTORY update flag controls whether or not + * file that are about to be deleted from the local file system have their + * current contents saved in the workspace's local history. The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying KEEP_HISTORY + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. Hence KEEP_HISTORY is only really applicable + * when moving files and folders, but not whole projects. + *

              + *

              + * If this resource is not a project, an attempt will be made to copy the local history + * for this resource and its children, to the destination. Since local history existence + * is a safety-net mechanism, failure of this action will not result in automatic failure + * of the move operation. + *

              + *

              + * The SHALLOW update flag controls how this method deals with linked + * resources. If SHALLOW is not specified, then the underlying + * contents of the linked resource will always be moved in the file system. In + * this case, the destination of the move will never be a linked resource or + * contain any linked resources. If SHALLOW is specified when a + * linked resource is moved into another project, a new linked resource is + * created in the destination project that points to the same file system + * location. When a project containing linked resources is moved, the new + * project will contain the same linked resources pointing to the same file + * system locations. For either of these cases, no files on disk under the + * linked resource are actually moved. With the SHALLOW flag, + * moving of linked resources into anything other than a project is not + * permitted. The SHALLOW update flag is ignored when moving non- + * linked resources. + *

              + *

              + * Update flags other than FORCE, KEEP_HISTORYand + * SHALLOW are ignored. + *

              + *

              + * This method changes resources; these changes will be reported in a subsequent + * resource change event that will include an indication that the resource has + * been removed from its parent and that a corresponding resource has been added + * to its new parent. Additional information provided with resource delta shows + * that these additions and removals are related. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param destination the destination path + * @param updateFlags bit-wise or of update flag constants + * (FORCE, KEEP_HISTORY and SHALLOW) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource or one of its descendents is not local.
              • + *
              • The source or destination is the workspace root.
              • + *
              • The source is a project but the destination is not.
              • + *
              • The destination is a project but the source is not.
              • + *
              • The resource corresponding to the parent destination path does not exist.
              • + *
              • The resource corresponding to the parent destination path is a closed + * project.
              • + *
              • The source is a linked resource, but the destination is not a project + * and SHALLOW is specified.
              • + *
              • A resource at destination path does exist.
              • + *
              • A resource of a different type exists at the destination path.
              • + *
              • This resource or one of its descendents is out of sync with the local file system + * and force is false.
              • + *
              • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
              • + *
              • The source resource is a file and the destination path specifies a project.
              • + *
              • The location of the source resource on disk is the same or a prefix of + * the location of the destination resource on disk.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + * @see #FORCE + * @see #KEEP_HISTORY + * @see #SHALLOW + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @since 2.0 + */ + public void move(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Renames or relocates this project so that it is the project specified by the given project + * description. + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   move(description, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
              +	 * 
              + *

              + *

              + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource has been removed from its parent + * and that a corresponding resource has been added to its new parent. + * Additional information provided with resource delta shows that these + * additions and removals are related. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param description the destination project description + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag indicating whether or not to keep + * local history for files + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource or one of its descendents is not local.
              • + *
              • This resource is not a project.
              • + *
              • The project at the destination already exists.
              • + *
              • This resource or one of its descendents is out of sync with the local file + * system and force is false.
              • + *
              • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + */ + public void move(IProjectDescription description, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Renames or relocates this project so that it is the project specified by the + * given project description. The description specifies the name and location + * of the new project. After successful completion, the old project + * and any direct or indirect members will no longer exist; but corresponding + * new resources will now exist in the new project. + *

              + * When a resource moves, its session and persistent properties move with it. + * Likewise for all the other attributes of the resource including markers. + *

              + *

              + * When this project's location is the default location, then the directories + * and files on disk are moved to be in the location specified by the given + * description. If the given description specifies the default location for the + * project, the directories and files are moved to the default location. If the name + * in the given description is the same as this project's name and the location + * is different, then the project contents will be moved to the new location. + * In all other cases the directories and files on disk are left untouched. + * Parts of the supplied description other than the name and location are ignored. + *

              + *

              + * The FORCE update flag controls how this method deals with cases + * where the workspace is not completely in sync with the local file system. If + * FORCE is not specified, the method will only attempt to move + * resources that are in sync with the corresponding files and directories in + * the local file system; it will fail if it encounters a resource that is out + * of sync with the file system. However, if FORCE is specified, + * the method moves all corresponding files and directories from the local file + * system, including ones that have been recently updated or created. Note that + * in both settings of the FORCE flag, the operation fails if the + * newly created resources in the workspace would be out of sync with the local + * file system; this ensures files in the file system cannot be accidentally + * overwritten. + *

              + *

              + * The KEEP_HISTORY update flag controls whether or not file that + * are about to be deleted from the local file system have their current + * contents saved in the workspace's local history. The local history mechanism + * serves as a safety net to help the user recover from mistakes that might + * otherwise result in data loss. Specifying KEEP_HISTORY is + * recommended except in circumstances where past states of the files are of no + * conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. Hence KEEP_HISTORY is only really applicable + * when moving files and folders, but not whole projects. + *

              + *

              + * Local history information for this project and its children will not be moved to the + * destination. + *

              + *

              + * The SHALLOW update flag controls how this method deals with linked + * resources. If SHALLOW is not specified, then the underlying + * contents of any linked resource will always be moved in the file system. In + * this case, the destination of the move will not contain any linked resources. + * If SHALLOW is specified when a project containing linked + * resources is moved, new linked resources are created in the destination + * project pointing to the same file system locations. In this case, no files + * on disk under any linked resource are actually moved. The + * SHALLOW update flag is ignored when moving non- linked + * resources. + *

              + *

              + * The {@link #REPLACE} update flag controls how this method deals + * with a change of location. If the location changes and the {@link #REPLACE} + * flag is not specified, then the projects contents on disk are moved to the new + * location. If the location changes and the {@link #REPLACE} + * flag is specified, then the project is reoriented to correspond to the new + * location, but no contents are moved on disk. The contents already on + * disk at the new location become the project contents. If the new project + * location does not exist, it will be created. + *

              + *

              + * Update flags other than those listed above are ignored. + *

              + *

              + * This method changes resources; these changes will be reported in a subsequent + * resource change event that will include an indication that the resource has + * been removed from its parent and that a corresponding resource has been added + * to its new parent. Additional information provided with resource delta shows + * that these additions and removals are related. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param description the destination project description + * @param updateFlags bit-wise or of update flag constants + * ({@link #FORCE}, {@link #KEEP_HISTORY}, {@link #SHALLOW} + * and {@link #REPLACE}). + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource or one of its descendents is not local.
              • + *
              • This resource is not a project.
              • + *
              • The project at the destination already exists.
              • + *
              • This resource or one of its descendents is out of sync with the + * local file system and FORCE is not specified.
              • + *
              • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              • The destination file system location is occupied. When moving a project + * in the file system, the destination directory must either not exist or be empty.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + * @see #FORCE + * @see #KEEP_HISTORY + * @see #SHALLOW + * @see #REPLACE + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @since 2.0 + */ + public void move(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Refreshes the resource hierarchy from this resource and its + * children (to the specified depth) relative to the local file system. + * Creations, deletions, and changes detected in the local file system + * will be reflected in the workspace's resource tree. + * This resource need not exist or be local. + *

              + * This method may discover changes to resources; any such + * changes will be reported in a subsequent resource change event. + *

              + *

              + * If a new file or directory is discovered in the local file + * system at or below the location of this resource, + * any parent folders required to contain the new + * resource in the workspace will also be created automatically as required. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param depth valid values are DEPTH_ZERO, + * DEPTH_ONE, or DEPTH_INFINITE + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceRuleFactory#refreshRule(IResource) + */ + public void refreshLocal(int depth, IProgressMonitor monitor) throws CoreException; + + /** + * Reverts this resource's modification stamp. This is intended to be used by + * a client that is rolling back or undoing a previous change to this resource. + *

              + * It is the caller's responsibility to ensure that the value of the reverted + * modification stamp matches this resource's modification stamp prior to the + * change that has been rolled back. More generally, the caller must ensure + * that the specification of modification stamps outlined in + * getModificationStamp is honored; the modification stamp + * of two distinct resource states should be different if and only if one or more + * of the attributes listed in the specification as affecting the modification + * stamp have changed. + *

              + * Reverting the modification stamp will not be reported in a + * subsequent resource change event. + *

              + * Note that a resource's modification stamp is unrelated to the local + * time stamp for this resource on disk, if any. A resource's local time + * stamp is modified using the setLocalTimeStamp method. + * + * @param value A non-negative modification stamp value + * @exception CoreException if this method fails. Reasons include: + *

                + *
              • This resource does not exist.
              • + *
              • This resource is not local.
              • + *
              • This resource is not accessible.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see #getModificationStamp() + * @since 3.1 + */ + public void revertModificationStamp(long value) throws CoreException; + + /** + * Sets whether this resource subtree is marked as derived. + *

              + * A derived resource is a regular file or folder that is + * created in the course of translating, compiling, copying, or otherwise + * processing other files. Derived resources are not original data, and can be + * recreated from other resources. It is commonplace to exclude derived + * resources from version and configuration management because they would + * otherwise clutter the team repository with version of these ever-changing + * files as each user regenerates them. + *

              + *

              + * If a resource or any of its ancestors is marked as derived, a team + * provider should assume that the resource is not under version and + * configuration management by default. That is, the resource + * should only be stored in a team repository if the user explicitly indicates + * that this resource is worth saving. + *

              + *

              + * Newly-created resources are not marked as derived; rather, the mark must be + * set explicitly using setDerived(true). Derived marks are maintained + * in the in-memory resource tree, and are discarded when the resources are deleted. + * Derived marks are saved to disk when a project is closed, or when the workspace + * is saved. + *

              + *

              + * Projects and the workspace root are never considered derived; attempts to + * mark them as derived are ignored. + *

              + *

              + * This operation does not result in a resource change event, and does not + * trigger autobuilds. + *

              + * + * @param isDerived true if this resource is to be marked + * as derived, and false otherwise + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see #isDerived() + * @since 2.0 + * @deprecated Replaced by {@link #setDerived(boolean, IProgressMonitor)} which + * is a workspace operation and reports changes in resource deltas. + */ + @Deprecated + public void setDerived(boolean isDerived) throws CoreException; + + /** + * Sets whether this resource subtree is marked as derived. + *

              + * A derived resource is a regular file or folder that is + * created in the course of translating, compiling, copying, or otherwise + * processing other files. Derived resources are not original data, and can be + * recreated from other resources. It is commonplace to exclude derived + * resources from version and configuration management because they would + * otherwise clutter the team repository with version of these ever-changing + * files as each user regenerates them. + *

              + *

              + * If a resource or any of its ancestors is marked as derived, a team + * provider should assume that the resource is not under version and + * configuration management by default. That is, the resource + * should only be stored in a team repository if the user explicitly indicates + * that this resource is worth saving. + *

              + *

              + * Newly-created resources are not marked as derived; rather, the mark must be + * set explicitly using setDerived(true, IProgressMonitor). Derived marks are maintained + * in the in-memory resource tree, and are discarded when the resources are deleted. + * Derived marks are saved to disk when a project is closed, or when the workspace + * is saved. + *

              + *

              + * Projects and the workspace root are never considered derived; attempts to + * mark them as derived are ignored. + *

              + *

              + * These changes will be reported in a subsequent resource change event, + * including an indication that this file's derived flag has changed. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param isDerived true if this resource is to be marked + * as derived, and false otherwise + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see #isDerived() + * @see IResourceRuleFactory#derivedRule(IResource) + * @since 3.6 + */ + public void setDerived(boolean isDerived, IProgressMonitor monitor) throws CoreException; + + /** + * Sets whether this resource and its members are hidden in the resource tree. + *

              + * Hidden resources are invisible to most clients. Newly-created resources + * are not hidden resources by default. + *

              + *

              + * The workspace root is never considered hidden resource; + * attempts to mark it as hidden are ignored. + *

              + *

              + * This operation does not result in a resource change event, and does not + * trigger autobuilds. + *

              + *

              + * This operation is not related to {@link ResourceAttributes#setHidden(boolean)}. + * Whether a resource is hidden in the resource tree is unrelated to whether the + * underlying file is hidden in the file system. + *

              + * + * @param isHidden true if this resource is to be marked + * as hidden, and false otherwise + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see #isHidden() + * @since 3.4 + */ + public void setHidden(boolean isHidden) throws CoreException; + + /** + * Set whether or not this resource and its members (to the + * specified depth) are expected to have their contents (and properties) + * available locally. The workspace root and projects are always local and + * attempting to set either to non-local (i.e., passing false) + * has no effect on the resource. + *

              + * When a resource is not local, its content and properties are + * unavailable for both reading and writing. + *

              + *

              + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param flag whether this resource should be considered local + * @param depth valid values are DEPTH_ZERO, + * DEPTH_ONE, or DEPTH_INFINITE + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See {@link IResourceChangeEvent} for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #isLocal(int) + * @deprecated This API is no longer in use. Note that this API is unrelated + * to whether the resource is in the local file system versus some other file system. + */ + @Deprecated + public void setLocal(boolean flag, int depth, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the local time stamp on disk for this resource. The time must be represented + * as the number of milliseconds since the epoch (00:00:00 GMT, January 1, 1970). + * Returns the actual time stamp that was recorded. + * Due to varying file system timing granularities, the provided value may be rounded + * or otherwise truncated, so the actual recorded time stamp that is returned may + * not be the same as the supplied value. + * + * @param value a time stamp in milliseconds. + * @return a local file system time stamp. + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is not local.
              • + *
              • This resource is not accessible.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @since 3.0 + */ + public long setLocalTimeStamp(long value) throws CoreException; + + /** + * Sets the value of the persistent property of this resource identified + * by the given key. If the supplied value is null, + * the persistent property is removed from this resource. The change + * is made immediately on disk. + *

              + * Persistent properties are intended to be used by plug-ins to store + * resource-specific information that should be persisted across platform sessions. + * The value of a persistent property is a string that must be short - + * 2KB or less in length. Unlike session properties, persistent properties are + * stored on disk and maintained across workspace shutdown and restart. + *

              + *

              + * 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 string value of the property, + * or null if the property is to be removed + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is not local.
              • + *
              • This resource is a project that is not open.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see #getPersistentProperty(QualifiedName) + * @see #isLocal(int) + */ + public void setPersistentProperty(QualifiedName key, String value) throws CoreException; + + /** + * Sets or unsets this resource as read-only in the file system. + * + * @param readOnly true to set it to read-only, + * false to unset + * @deprecated use IResource#setResourceAttributes(ResourceAttributes) + */ + @Deprecated + public void setReadOnly(boolean readOnly); + + /** + * Sets this resource with the given extended attributes. This sets the + * attributes in the file system. Only attributes that are supported by + * the underlying file system will be set. + *

              + * Sample usage:
              + *
              + * + * IResource resource;
              + * ...
              + * if (attributes != null) { + * attributes.setExecutable(true);
              + * resource.setResourceAttributes(attributes);
              + * } + *
              + *

              + *

              + * Note that a resource cannot be converted into a symbolic link by + * setting resource attributes with {@link ResourceAttributes#isSymbolicLink()} + * set to true. + *

              + * + * @param attributes the attributes to set + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is not local.
              • + *
              • This resource is a project that is not open.
              • + *
              + * @see #getResourceAttributes() + * @since 3.1 + */ + void setResourceAttributes(ResourceAttributes attributes) throws CoreException; + + /** + * Sets the value of the session property of this resource identified + * by the given key. If the supplied value is null, + * the session property is removed from this resource. + *

              + * Sessions properties are intended to be used as a caching mechanism + * by ISV plug-ins. They allow key-object associations to be stored with + * existing resources in the workspace. These key-value associations are + * maintained in memory (at all times), and the information is lost when a + * resource is deleted from the workspace, when the parent project + * is closed, or when the workspace is closed. + *

              + *

              + * 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 session property, + * or null if the property is to be removed + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is not local.
              • + *
              • This resource is a project that is not open.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see #getSessionProperty(QualifiedName) + */ + public void setSessionProperty(QualifiedName key, Object value) throws CoreException; + + /** + * Sets whether this resource subtree is a team private member of its parent container. + *

              + * A team private member resource is a special file or folder created by a team + * provider to hold team-provider-specific information. Resources marked as team private + * members are invisible to most clients. + *

              + *

              + * Newly-created resources are not team private members by default; rather, the + * team provider must mark a resource explicitly using + * setTeamPrivateMember(true). Team private member marks are + * maintained in the in-memory resource tree, and are discarded when the + * resources are deleted. Team private member marks are saved to disk when a + * project is closed, or when the workspace is saved. + *

              + *

              + * Projects and the workspace root are never considered team private members; + * attempts to mark them as team private are ignored. + *

              + *

              + * This operation does not result in a resource change event, and does not + * trigger autobuilds. + *

              + * + * @param isTeamPrivate true if this resource is to be marked + * as team private, and false otherwise + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @see #isTeamPrivateMember() + * @since 2.0 + */ + public void setTeamPrivateMember(boolean isTeamPrivate) throws CoreException; + + /** + * Marks this resource as having changed even though its content + * may not have changed. This method can be used to trigger + * the rebuilding of resources/structures derived from this resource. + * Touching the workspace root has no effect. + *

              + * This method changes resources; these changes will be reported + * in a subsequent resource change event. If the resource is a project, + * the change event will indicate a description change. + *

              + *

              + * 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 + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • This resource does not exist.
              • + *
              • This resource is not local.
              • + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @see IResourceDelta#CONTENT + * @see IResourceDelta#DESCRIPTION + */ + public void touch(IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java new file mode 100644 index 0000000000..d3bdaa1e1a --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java @@ -0,0 +1,266 @@ +/******************************************************************************* + * 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; + +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * Resource change events describe changes to resources. + *

              + * There are currently five different types of resource change events: + *

                + *
              • + * Before-the-fact batch reports of arbitrary creations, + * deletions and modifications to one or more resources expressed + * as a hierarchical resource delta. Event type is + * PRE_BUILD, and getDelta returns + * the hierarchical delta rooted at the workspace root. + * The getBuildKind method returns + * the kind of build that is about to occur, and the getSource + * method returns the scope of the build (either the workspace or a single project). + * These events are broadcast to interested parties immediately + * before each build operation. If autobuilding is not enabled, these events still + * occur at times when autobuild would have occurred. The workspace is open + * for change during notification of these events. The delta reported in this event + * cycle is identical across all listeners registered for this type of event. + * Resource changes attempted during a PRE_BUILD callback + * must be done in the thread doing the notification. + *
              • + *
              • + * After-the-fact batch reports of arbitrary creations, + * deletions and modifications to one or more resources expressed + * as a hierarchical resource delta. Event type is + * POST_BUILD, and getDelta returns + * the hierarchical delta rooted at the workspace root. + * The getBuildKind method returns + * the kind of build that occurred, and the getSource + * method returns the scope of the build (either the workspace or a single project). + * These events are broadcast to interested parties at the end of every build operation. + * If autobuilding is not enabled, these events still occur at times when autobuild + * would have occurred. The workspace is open for change during notification of + * these events. The delta reported in this event cycle is identical across + * all listeners registered for this type of event. + * Resource changes attempted during a POST_BUILD callback + * must be done in the thread doing the notification. + *
              • + *
              • + * After-the-fact batch reports of arbitrary creations, + * deletions and modifications to one or more resources expressed + * as a hierarchical resource delta. Event type is + * POST_CHANGE, and getDelta returns + * the hierarchical delta. The resource delta is rooted at the + * workspace root. These events are broadcast to interested parties after + * a set of resource changes and happen whether or not autobuilding is enabled. + * The workspace is closed for change during notification of these events. + * The delta reported in this event cycle is identical across all listeners registered for + * this type of event. + *
              • + *
              • + * Before-the-fact reports of the impending closure of a single + * project. Event type is PRE_CLOSE, + * and getResource returns the project being closed. + * The workspace is closed for change during notification of these events. + *
              • + *
              • + * Before-the-fact reports of the impending deletion of a single + * project. Event type is PRE_DELETE, + * and getResource returns the project being deleted. + * The workspace is closed for change during notification of these events. + *
              • + *
              • + * Before-the-fact reports of the impending refresh of a single project or the workspace. + * Event type is PRE_REFRESH and the getSource + * method returns the scope of the refresh (either the workspace or a single project). + * If the event is fired by a project refresh the getResource + * method returns the project being refreshed. + * The workspace is closed for changes during notification of these events. + *
              • + *
              + *

              + * In order to handle additional event types that may be introduced + * in future releases of the platform, clients should do not write code + * that presumes the set of event types is closed. + *

              + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceChangeEvent { + /** + * Event type constant (bit mask) indicating an after-the-fact + * report of creations, deletions, and modifications + * to one or more resources expressed as a hierarchical + * resource delta as returned by getDelta. + * See class comments for further details. + * + * @see #getType() + * @see #getDelta() + */ + public static final int POST_CHANGE = 1; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of the impending closure of a single + * project as returned by getResource. + * See class comments for further details. + * + * @see #getType() + * @see #getResource() + */ + public static final int PRE_CLOSE = 2; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of the impending deletion of a single + * project as returned by getResource. + * See class comments for further details. + * + * @see #getType() + * @see #getResource() + */ + public static final int PRE_DELETE = 4; + + /** + * @deprecated This event type has been renamed to + * PRE_BUILD + */ + @Deprecated + public static final int PRE_AUTO_BUILD = 8; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of a build. The event contains a hierarchical resource delta + * as returned by getDelta. + * See class comments for further details. + * + * @see #getBuildKind() + * @see #getSource() + * @since 3.0 + */ + public static final int PRE_BUILD = 8; + + /** + * @deprecated This event type has been renamed to + * POST_BUILD + */ + @Deprecated + public static final int POST_AUTO_BUILD = 16; + + /** + * Event type constant (bit mask) indicating an after-the-fact + * report of a build. The event contains a hierarchical resource delta + * as returned by getDelta. + * See class comments for further details. + * + * @see #getBuildKind() + * @see #getSource() + * @since 3.0 + */ + public static final int POST_BUILD = 16; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of refreshing the workspace or a project. + * See class comments for further details. + * + * @see #getType() + * @see #getSource() + * @see #getResource() + * @since 3.4 + */ + public static final int PRE_REFRESH = 32; + + /** + * Returns all marker deltas of the specified type that are associated + * with resource deltas for this event. If includeSubtypes + * is false, only marker deltas whose type exactly matches + * the given type are returned. Returns an empty array if there + * are no matching marker deltas. + *

              + * Calling this method is equivalent to walking the entire resource + * delta for this event, and collecting all marker deltas of a given type. + * The speed of this method will be proportional to the number of changed + * markers, regardless of the size of the resource delta tree. + *

              + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @return an array of marker deltas + * @since 2.0 + */ + public IMarkerDelta[] findMarkerDeltas(String type, boolean includeSubtypes); + + /** + * Returns the kind of build that caused this event, + * or 0 if not applicable to this type of event. + *

              + * If the event is a PRE_BUILD or POST_BUILD + * then this will be the kind of build that occurred to cause the event. + *

              + * + * @see IProject#build(int, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + * @see IncrementalProjectBuilder#AUTO_BUILD + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @return the kind of build, or 0 if not applicable + * @since 3.1 + */ + public int getBuildKind(); + + /** + * Returns a resource delta, rooted at the workspace, describing the set + * of changes that happened to resources in the workspace. + * Returns null if not applicable to this type of event. + * + * @return the resource delta, or null if not + * applicable + */ + public IResourceDelta getDelta(); + + /** + * Returns the resource in question or null + * if not applicable to this type of event. + *

              + * If the event is of type PRE_CLOSE, + * PRE_DELETE, or PRE_REFRESH, then the resource + * will be the affected project. Otherwise the resource will be null. + *

              + * @return the resource, or null if not applicable + */ + public IResource getResource(); + + /** + * Returns an object identifying the source of this event. + *

              + * If the event is a PRE_BUILD, POST_BUILD, + * or PRE_REFRESH then this will be the scope of the build + * (either the {@link IWorkspace} or a single {@link IProject}). + *

              + * + * @return an object identifying the source of this event + * @see java.util.EventObject + */ + public Object getSource(); + + /** + * Returns the type of event being reported. + * + * @return one of the event type constants + * @see #POST_CHANGE + * @see #POST_BUILD + * @see #PRE_BUILD + * @see #PRE_CLOSE + * @see #PRE_DELETE + * @see #PRE_REFRESH + */ + public int getType(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java new file mode 100644 index 0000000000..f441aa58d5 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.util.EventListener; + +/** + * A resource change listener is notified of changes to resources + * in the workspace. + * These changes arise from direct manipulation of resources, or + * indirectly through re-synchronization with the local file system. + *

              + * Clients may implement this interface. + *

              + * @see IResourceDelta + * @see IWorkspace#addResourceChangeListener(IResourceChangeListener, int) + */ +public interface IResourceChangeListener extends EventListener { + /** + * Notifies this listener that some resource changes + * are happening, or have already happened. + *

              + * The supplied event gives details. This event object (and the + * resource delta within it) is valid only for the duration of + * the invocation of this method. + *

              + *

              + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

              + * Note that during resource change event notification, further changes + * to resources may be disallowed. + *

              + * + * @param event the resource change event + * @see IResourceDelta + */ + public void resourceChanged(IResourceChangeEvent event); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java new file mode 100644 index 0000000000..a12076fb74 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java @@ -0,0 +1,591 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.internal.watson.IElementComparator; +import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory; +import org.eclipse.core.runtime.*; + +/** + * A resource delta represents changes in the state of a resource tree + * between two discrete points in time. + *

              + * Resource deltas implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

              + * + * @see IResource + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceDelta extends IAdaptable { + + /*==================================================================== + * Constants defining resource delta kinds: + *====================================================================*/ + + /** + * Delta kind constant indicating that the resource has not been changed in any way. + * + * @see IResourceDelta#getKind() + */ + public static final int NO_CHANGE = IElementComparator.K_NO_CHANGE; + + /** + * Delta kind constant (bit mask) indicating that the resource has been added + * to its parent. That is, one that appears in the "after" state, + * not in the "before" one. + * + * @see IResourceDelta#getKind() + */ + public static final int ADDED = 0x1; + + /** + * Delta kind constant (bit mask) indicating that the resource has been removed + * from its parent. That is, one that appears in the "before" state, + * not in the "after" one. + * + * @see IResourceDelta#getKind() + */ + public static final int REMOVED = 0x2; + + /** + * Delta kind constant (bit mask) indicating that the resource has been changed. + * That is, one that appears in both the "before" and "after" states. + * + * @see IResourceDelta#getKind() + */ + public static final int CHANGED = 0x4; + + /** + * Delta kind constant (bit mask) indicating that a phantom resource has been added at + * the location of the delta node. + * + * @see IResourceDelta#getKind() + */ + public static final int ADDED_PHANTOM = 0x8; + + /** + * Delta kind constant (bit mask) indicating that a phantom resource has been removed from + * the location of the delta node. + * + * @see IResourceDelta#getKind() + */ + public static final int REMOVED_PHANTOM = 0x10; + + /** + * The bit mask which describes all possible delta kinds, + * including ones involving phantoms. + * + * @see IResourceDelta#getKind() + */ + public static final int ALL_WITH_PHANTOMS = CHANGED | ADDED | REMOVED | ADDED_PHANTOM | REMOVED_PHANTOM; + + /*==================================================================== + * Constants which describe resource changes: + *====================================================================*/ + + /** + * Change constant (bit mask) indicating that the content of the resource has changed. + * + * @see IResourceDelta#getFlags() + */ + public static final int CONTENT = 0x100; + + /** + * Change constant (bit mask) indicating that the resource was moved from another location. + * The location in the "before" state can be retrieved using getMovedFromPath(). + * + * @see IResourceDelta#getFlags() + */ + public static final int MOVED_FROM = 0x1000; + + /** + * Change constant (bit mask) indicating that the resource was moved to another location. + * The location in the new state can be retrieved using getMovedToPath(). + * + * @see IResourceDelta#getFlags() + */ + public static final int MOVED_TO = 0x2000; + + /** + * Change constant (bit mask) indicating that the resource was copied from another location. + * The location in the "before" state can be retrieved using getMovedFromPath(). + * This flag is only used when describing potential changes using an {@link IResourceChangeDescriptionFactory}. + * + * @see IResourceDelta#getFlags() + * @since 3.2 + */ + public static final int COPIED_FROM = 0x800; + /** + * Change constant (bit mask) indicating that the resource was opened or closed. + * This flag is also set when the project did not exist in the "before" state. + * For example, if the current state of the resource is open then it was previously closed + * or did not exist. + * + * @see IResourceDelta#getFlags() + */ + public static final int OPEN = 0x4000; + + /** + * Change constant (bit mask) indicating that the type of the resource has changed. + * + * @see IResourceDelta#getFlags() + */ + public static final int TYPE = 0x8000; + + /** + * Change constant (bit mask) indicating that the resource's sync status has changed. + * This type of change is not included in build deltas, only in those for resource notification. + * + * @see IResourceDelta#getFlags() + */ + public static final int SYNC = 0x10000; + + /** + * Change constant (bit mask) indicating that the resource's markers have changed. + * This type of change is not included in build deltas, only in those for resource notification. + * + * @see IResourceDelta#getFlags() + */ + public static final int MARKERS = 0x20000; + + /** + * Change constant (bit mask) indicating that the resource has been + * replaced by another at the same location (i.e., the resource has + * been deleted and then added). + * + * @see IResourceDelta#getFlags() + */ + public static final int REPLACED = 0x40000; + + /** + * Change constant (bit mask) indicating that a project's description has changed. + * + * @see IResourceDelta#getFlags() + */ + public static final int DESCRIPTION = 0x80000; + + /** + * Change constant (bit mask) indicating that the encoding of the resource has changed. + * + * @see IResourceDelta#getFlags() + * @since 3.0 + */ + public static final int ENCODING = 0x100000; + + /** + * Change constant (bit mask) indicating that the underlying file or folder of the linked resource has been added or removed. + * + * @see IResourceDelta#getFlags() + * @since 3.4 + */ + public static final int LOCAL_CHANGED = 0x200000; + + /** + * Change constant (bit mask) indicating that the derived flag of the resource has changed. + * + * @see IResourceDelta#getFlags() + * @since 3.6 + */ + public static final int DERIVED_CHANGED = 0x400000; + + /** + * Accepts the given visitor. + * The only kinds of resource deltas visited + * are ADDED, REMOVED, + * and CHANGED. + * The visitor's visit method is called with this + * resource delta if applicable. If the visitor returns true, + * the resource delta's children are also visited. + *

              + * This is a convenience method, fully equivalent to + * accept(visitor, IResource.NONE). + * Although the visitor will be invoked for this resource delta, it will not be + * invoked for any team-private member resources. + *

              + * + * @param visitor the visitor + * @exception CoreException if the visitor failed with this exception. + * @see IResourceDeltaVisitor#visit(IResourceDelta) + */ + public void accept(IResourceDeltaVisitor visitor) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource delta. If the visitor returns true, + * the resource delta's children are also visited. + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   accept(visitor, includePhantoms ? INCLUDE_PHANTOMS : IResource.NONE);
              +	 * 
              + * Although the visitor will be invoked for this resource delta, it will not be + * invoked for any team-private member resources. + *

              + * + * @param visitor the visitor + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @exception CoreException if the visitor failed with this exception. + * @see #accept(IResourceDeltaVisitor) + * @see IResource#isPhantom() + * @see IResourceDeltaVisitor#visit(IResourceDelta) + */ + public void accept(IResourceDeltaVisitor visitor, boolean includePhantoms) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource delta. If the visitor returns true, + * the resource delta's children are also visited. + *

              + * The member flags determine which child deltas of this resource delta will be visited. + * The visitor will always be invoked for this resource delta. + *

              + * If the INCLUDE_PHANTOMS member flag is not specified + * (recommended), only child resource deltas involving existing resources will be visited + * (kinds ADDED, REMOVED, and CHANGED). + * If the INCLUDE_PHANTOMS member flag is specified, + * the result will also include additions and removes of phantom resources + * (kinds ADDED_PHANTOM and REMOVED_PHANTOM). + *

              + *

              + * If the INCLUDE_TEAM_PRIVATE_MEMBERS member flag is not specified + * (recommended), resource deltas involving team private member resources will be + * excluded from the visit. If the INCLUDE_TEAM_PRIVATE_MEMBERS member + * flag is specified, the visit will also include additions and removes of + * team private member resources. + *

              + * + * @param visitor the visitor + * @param memberFlags bit-wise or of member flag constants + * (IContainer.INCLUDE_PHANTOMS, INCLUDE_HIDDEN + * and INCLUDE_TEAM_PRIVATE_MEMBERS) indicating which members are of interest + * @exception CoreException if the visitor failed with this exception. + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResource#isHidden() + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IResourceDeltaVisitor#visit(IResourceDelta) + * @since 2.0 + */ + public void accept(IResourceDeltaVisitor visitor, int memberFlags) throws CoreException; + + /** + * Finds and returns the descendent delta identified by the given path in + * this delta, or null if no such descendent exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this delta. Trailing separators are ignored. + * If the path is empty this delta is returned. + *

              + * This is a convenience method to avoid manual traversal of the delta + * tree in cases where the listener is only interested in changes to + * particular resources. Calling this method will generally be + * faster than manually traversing the delta to a particular descendent. + *

              + * @param path the path of the desired descendent delta + * @return the descendent delta, or null if no such + * descendent exists in the delta + * @since 2.0 + */ + public IResourceDelta findMember(IPath path); + + /** + * Returns resource deltas for all children of this resource + * which were added, removed, or changed. Returns an empty + * array if there are no affected children. + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   getAffectedChildren(ADDED | REMOVED | CHANGED, IResource.NONE);
              +	 * 
              + * Team-private member resources are not included in the result; neither are + * phantom resources. + *

              + * + * @return the resource deltas for all affected children + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see #getAffectedChildren(int,int) + */ + public IResourceDelta[] getAffectedChildren(); + + /** + * Returns resource deltas for all children of this resource + * whose kind is included in the given mask. Kind masks are formed + * by the bitwise or of IResourceDelta kind constants. + * Returns an empty array if there are no affected children. + *

              + * This is a convenience method, fully equivalent to: + *

              +	 *   getAffectedChildren(kindMask, IResource.NONE);
              +	 * 
              + * Team-private member resources are not included in the result. + *

              + * + * @param kindMask a mask formed by the bitwise or of IResourceDelta + * delta kind constants + * @return the resource deltas for all affected children + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see IResourceDelta#ADDED_PHANTOM + * @see IResourceDelta#REMOVED_PHANTOM + * @see IResourceDelta#ALL_WITH_PHANTOMS + * @see #getAffectedChildren(int,int) + */ + public IResourceDelta[] getAffectedChildren(int kindMask); + + /** + * Returns resource deltas for all children of this resource + * whose kind is included in the given mask. Masks are formed + * by the bitwise or of IResourceDelta kind constants. + * Returns an empty array if there are no affected children. + *

              + * If the INCLUDE_TEAM_PRIVATE_MEMBERS member flag is not specified, + * (recommended), resource deltas involving team private member resources will be + * excluded. If the INCLUDE_TEAM_PRIVATE_MEMBERS member + * flag is specified, the result will also include resource deltas of the + * specified kinds to team private member resources. + *

              + *

              + * If the {@link IContainer#INCLUDE_HIDDEN} member flag is not specified, + * (recommended), resource deltas involving hidden resources will be + * excluded. If the {@link IContainer#INCLUDE_HIDDEN} member + * flag is specified, the result will also include resource deltas of the + * specified kinds to hidden resources. + *

              + *

              + * Specifying the IContainer.INCLUDE_PHANTOMS member flag is equivalent + * to including IContainer.ADDED_PHANTOM and IContainer.REMOVED_PHANTOM + * in the kind mask. + *

              + * + * @param kindMask a mask formed by the bitwise or of IResourceDelta + * delta kind constants + * @param memberFlags bit-wise or of member flag constants + * (IContainer.INCLUDE_PHANTOMS, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS + * and IContainer.INCLUDE_HIDDEN) + * indicating which members are of interest + * @return the resource deltas for all affected children + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see IResourceDelta#ADDED_PHANTOM + * @see IResourceDelta#REMOVED_PHANTOM + * @see IResourceDelta#ALL_WITH_PHANTOMS + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @since 2.0 + */ + public IResourceDelta[] getAffectedChildren(int kindMask, int memberFlags); + + /** + * Returns flags which describe in more detail how a resource has been affected. + *

              + * The following codes (bit masks) are used when kind is CHANGED, and + * also when the resource is involved in a move: + *

                + *
              • CONTENT - The bytes contained by the resource have + * been altered, or IResource.touch has been called on + * the resource.
              • + *
              • DERIVED_CHANGED - The derived flag of the resource has + * been altered.
              • + *
              • ENCODING - The encoding of the resource may have been altered. + * This flag is not set when the encoding changes due to the file being modified, + * or being moved.
              • + *
              • DESCRIPTION - The description of the project has been altered, + * or IResource.touch has been called on the project. + * This flag is only valid for project resources.
              • + *
              • OPEN - The project's open/closed state has changed. + * If it is not open, it was closed, and vice versa. This flag is only valid for project resources.
              • + *
              • TYPE - The resource (a folder or file) has changed its type.
              • + *
              • SYNC - The resource's sync status has changed.
              • + *
              • MARKERS - The resource's markers have changed.
              • + *
              • REPLACED - The resource (and all its properties) + * was deleted (either by a delete or move), and was subsequently re-created + * (either by a create, move, or copy).
              • + *
              • LOCAL_CHANGED - The resource is a linked resource, + * and the underlying file system object has been added or removed.
              • + *
              + * The following code is only used if kind is REMOVED + * (or CHANGED in conjunction with REPLACED): + *
                + *
              • MOVED_TO - The resource has moved. + * getMovedToPath will return the path of where it was moved to.
              • + *
              + * The following code is only used if kind is ADDED + * (or CHANGED in conjunction with REPLACED): + *
                + *
              • MOVED_FROM - The resource has moved. + * getMovedFromPath will return the path of where it was moved from.
              • + *
              + * The following code is only used when describing potential changes using an {@link IResourceChangeDescriptionFactory}: + *
                + *
              • COPIED_FROM - Change constant (bit mask) indicating that the resource was copied from another location. + * The location in the "before" state can be retrieved using getMovedFromPath().
              • + *
              + * + * A simple move operation would result in the following delta information. + * If a resource is moved from A to B (with no other changes to A or B), + * then A will have kind REMOVED, with flag MOVED_TO, + * and getMovedToPath on A will return the path for B. + * B will have kind ADDED, with flag MOVED_FROM, + * and getMovedFromPath on B will return the path for A. + * B's other flags will describe any other changes to the resource, as compared + * to its previous location at A. + *

              + *

              + * Note that the move flags only describe the changes to a single resource; they + * don't necessarily imply anything about the parent or children of the resource. + * If the children were moved as a consequence of a subtree move operation, + * they will have corresponding move flags as well. + *

              + *

              + * Note that it is possible for a file resource to be replaced in the workspace + * by a folder resource (or the other way around). + * The resource delta, which is actually expressed in terms of + * paths instead or resources, shows this as a change to either the + * content or children. + *

              + * + * @return the flags + * @see IResourceDelta#CONTENT + * @see IResourceDelta#DERIVED_CHANGED + * @see IResourceDelta#DESCRIPTION + * @see IResourceDelta#ENCODING + * @see IResourceDelta#LOCAL_CHANGED + * @see IResourceDelta#OPEN + * @see IResourceDelta#MOVED_TO + * @see IResourceDelta#MOVED_FROM + * @see IResourceDelta#COPIED_FROM + * @see IResourceDelta#TYPE + * @see IResourceDelta#SYNC + * @see IResourceDelta#MARKERS + * @see IResourceDelta#REPLACED + * @see #getKind() + * @see #getMovedFromPath() + * @see #getMovedToPath() + * @see IResource#move(IPath, int, IProgressMonitor) + */ + public int getFlags(); + + /** + * Returns the full, absolute path of this resource delta. + *

              + * Note: the returned path never has a trailing separator. + *

              + * @return the full, absolute path of this resource delta + * @see IResource#getFullPath() + * @see #getProjectRelativePath() + */ + public IPath getFullPath(); + + /** + * Returns the kind of this resource delta. + * Normally, one of ADDED, + * REMOVED, CHANGED. + * When phantom resources have been explicitly requested, + * there are two additional kinds: ADDED_PHANTOM + * and REMOVED_PHANTOM. + * + * @return the kind of this resource delta + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see IResourceDelta#ADDED_PHANTOM + * @see IResourceDelta#REMOVED_PHANTOM + */ + public int getKind(); + + /** + * Returns the changes to markers on the corresponding resource. + * Returns an empty array if no markers changed. + * + * @return the marker deltas + */ + public IMarkerDelta[] getMarkerDeltas(); + + /** + * Returns the full path (in the "before" state) from which this resource + * (in the "after" state) was moved. This value is only valid + * if the MOVED_FROM change flag is set; otherwise, + * null is returned. + *

              + * Note: the returned path never has a trailing separator. + * + * @return a path, or null + * @see #getMovedToPath() + * @see #getFullPath() + * @see #getFlags() + */ + public IPath getMovedFromPath(); + + /** + * Returns the full path (in the "after" state) to which this resource + * (in the "before" state) was moved. This value is only valid if the + * MOVED_TO change flag is set; otherwise, + * null is returned. + *

              + * Note: the returned path never has a trailing separator. + * + * @return a path, or null + * @see #getMovedFromPath() + * @see #getFullPath() + * @see #getFlags() + */ + public IPath getMovedToPath(); + + /** + * Returns the project-relative path of this resource delta. + * Returns the empty path for projects and the workspace root. + *

              + * A resource's project-relative path indicates the route from the project + * to the resource. Within a workspace, there is exactly one such path + * for any given resource. The returned path never has a trailing separator. + *

              + * @return the project-relative path of this resource delta + * @see IResource#getProjectRelativePath() + * @see #getFullPath() + * @see Path#EMPTY + */ + public IPath getProjectRelativePath(); + + /** + * Returns a handle for the affected resource. + *

              + * For additions (ADDED), this handle describes the newly-added resource; i.e., + * the one in the "after" state. + *

              + * For changes (CHANGED), this handle also describes the resource in the "after" + * state. When a file or folder resource has changed type, the + * former type of the handle can be inferred. + *

              + * For removals (REMOVED), this handle describes the resource in the "before" + * state. Even though this resource would not normally exist in the + * current workspace, the type of resource that was removed can be + * determined from the handle. + *

              + * For phantom additions and removals (ADDED_PHANTOM + * and REMOVED_PHANTOM), this is the handle of the phantom resource. + * + * @return the affected resource (handle) + */ + public IResource getResource(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java new file mode 100644 index 0000000000..c1824355d5 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * An objects that visits resource deltas. + *

              + * Usage: + *

              + * class Visitor implements IResourceDeltaVisitor {
              + *     public boolean visit(IResourceDelta delta) {
              + *         switch (delta.getKind()) {
              + *         case IResourceDelta.ADDED :
              + *             // handle added resource
              + *             break;
              + *         case IResourceDelta.REMOVED :
              + *             // handle removed resource
              + *             break;
              + *         case IResourceDelta.CHANGED :
              + *             // handle changed resource
              + *             break;
              + *         }
              + *     return true;
              + *     }
              + * }
              + * IResourceDelta rootDelta = ...;
              + * rootDelta.accept(new Visitor());
              + * 
              + *

              + *

              + * Clients may implement this interface. + *

              + * + * @see IResource#accept(IResourceVisitor) + */ +public interface IResourceDeltaVisitor { + /** + * Visits the given resource delta. + * + * @return true if the resource delta's children should + * be visited; false if they should be skipped. + * @exception CoreException if the visit fails for some reason. + */ + public boolean visit(IResourceDelta delta) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java new file mode 100644 index 0000000000..669142b3a8 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 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 - ongoing development + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.runtime.*; + +/** + * A description of a resource filter. + * + * A filter determines which file system objects will be visible when a local refresh is + * performed for an IContainer. + * + * @see IContainer#getFilters() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * @since 3.6 + */ +public interface IResourceFilterDescription { + + /*==================================================================== + * Constants defining which members are wanted: + *====================================================================*/ + + /** + * Flag for resource filters indicating that the filter list includes only + * the files matching the filters. All INCLUDE_ONLY filters are applied to + * the resource list with an logical OR operation. + */ + public static final int INCLUDE_ONLY = 1; + + /** + * Flag for resource filters indicating that the filter list excludes all + * the files matching the filters. All EXCLUDE_ALL filters are applied to + * the resource list with an logical AND operation. + */ + public static final int EXCLUDE_ALL = 2; + + /** + * Flag for resource filters indicating that this filter applies to files. + */ + public static final int FILES = 4; + + /** + * Flag for resource filters indicating that this filter applies to folders. + */ + public static final int FOLDERS = 8; + + /** + * Flag for resource filters indicating that the container children of the + * path inherit from this filter as well. + */ + public static final int INHERITABLE = 16; + + /** + * Returns the description of the file info matcher corresponding to this resource + * filter. + * @return the file info matcher description for this resource filter + */ + public FileInfoMatcherDescription getFileInfoMatcherDescription(); + + /** + * Return the resource towards which this filter is set. + * + * @return the resource towards which this filter is set + */ + public IResource getResource(); + + /** + * Return the filter type, either INCLUDE_ONLY or EXCLUDE_ALL + * + * @return (INCLUDE_ONLY or EXCLUDE_ALL) and/or INHERITABLE + */ + public int getType(); + + /** + * Deletes this filter description from its associated resource. + *

              + * The {@link IResource#BACKGROUND_REFRESH} update flag controls when + * changes to the resource hierarchy under this container resulting from the filter + * removal take effect. If this flag is specified, the resource hierarchy is updated in a + * separate thread after this method returns. If the flag is not specified, any resource + * changes resulting from the filter removal will occur before this method returns. + *

              + *

              + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include an indication + * of any resources that have been added as a result of the filter removal. + *

              + *

              + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

              + * + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#BACKGROUND_REFRESH}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this filter could not be removed. Reasons include: + *
                + *
              • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IContainer#getFilters() + * @see IContainer#createFilter(int, FileInfoMatcherDescription, int, IProgressMonitor) + */ + public void delete(int updateFlags, IProgressMonitor monitor) throws CoreException; + +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java new file mode 100644 index 0000000000..28939816a4 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.QualifiedName; + +/** + * A lightweight interface for requesting information about a resource. + * All of the "get" methods on a resource proxy have trivial performance cost. + * Requesting the full path or the actual resource handle will cause extra objects + * to be created and will thus have greater cost. + *

              + * When a resource proxy is used within an {@link IResourceProxyVisitor}, + * it is a transient object that is only valid for the duration of a single visit method. + * A proxy should not be referenced once the single resource visit is complete. + * The equals and hashCode methods should not be relied on. + *

              + *

              + * A proxy can also be created using {@link IResource#createProxy()}. In + * this case the proxy is valid indefinitely, but will not remain in sync with + * the state of the corresponding resource. + *

              + * + * @see IResourceProxyVisitor + * @since 2.1 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceProxy { + /** + * Returns the modification stamp of the resource being visited. + * + * @return the modification stamp, or NULL_STAMP if the + * resource either does not exist or exists as a closed project + * @see IResource#getModificationStamp() + */ + public long getModificationStamp(); + + /** + * Returns whether the resource being visited is accessible. + * + * @return true if the resource is accessible, and + * false otherwise + * @see IResource#isAccessible() + */ + public boolean isAccessible(); + + /** + * Returns whether the resource being visited is derived. + * + * @return true if the resource is marked as derived, and + * false otherwise + * @see IResource#isDerived() + */ + public boolean isDerived(); + + /** + * Returns whether the resource being visited is a linked resource. + * + * @return true if the resource is linked, and + * false otherwise + * @see IResource#isLinked() + */ + public boolean isLinked(); + + /** + * Returns whether the resource being visited is a phantom resource. + * + * @return true if the resource is a phantom resource, and + * false otherwise + * @see IResource#isPhantom() + */ + public boolean isPhantom(); + + /** + * Returns whether the resource being visited is a hidden resource. + * + * @return true if the resource is a hidden resource, and + * false otherwise + * @see IResource#isHidden() + * + * @since 3.4 + */ + public boolean isHidden(); + + /** + * Returns whether the resource being visited is a team private member. + * + * @return true if the resource is a team private member, and + * false otherwise + * @see IResource#isTeamPrivateMember() + */ + public boolean isTeamPrivateMember(); + + /** + * Returns the simple name of the resource being visited. + * + * @return the name of the resource + * @see IResource#getName() + */ + public String getName(); + + /** + * Returns the value of the session property of the resource being + * visited, identified by the given key. Returns null if this + * resource has no such property. + *

              + * Note that this method can return an out of date property value, or a + * value that no longer exists, if session properties are being modified + * concurrently with the resource visit. + *

              + * + * @param key the qualified name of the property + * @return the string value of the session property, + * or null if the resource has no such property + * @see IResource#getSessionProperty(QualifiedName) + */ + public Object getSessionProperty(QualifiedName key); + + /** + * Returns the type of the resource being visited. + * + * @return the resource type + * @see IResource#getType() + */ + public int getType(); + + /** + * Returns the full workspace path of the resource being visited. + *

              + * Note that this is not a "free" proxy operation. This method + * will generally cause a path object to be created. For an optimal + * visitor, only call this method when absolutely necessary. Note that the + * simple resource name can be obtained from the proxy with no cost. + *

              + * @return the full path of the resource + * @see IResource#getFullPath() + */ + public IPath requestFullPath(); + + /** + * Returns the handle of the resource being visited. + *

              + * Note that this is not a "free" proxy operation. This method will + * generally cause both a path object and a resource object to be created. + * For an optimal visitor, only call this method when absolutely necessary. + * Note that the simple resource name can be obtained from the proxy with no + * cost, and the full path of the resource can be obtained through the proxy + * with smaller cost. + *

              + * @return the resource handle + */ + public IResource requestResource(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java new file mode 100644 index 0000000000..583b72cbb4 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * This interface is implemented by objects that visit resource trees. The fast + * visitor is an optimized mechanism for tree traversal that creates a minimal + * number of objects. The visitor is provided with a callback interface, + * instead of a resource. Through the callback, the visitor can request + * information about the resource being visited. + *

              + * Usage: + *

              + * class Visitor implements IResourceProxyVisitor {
              + * 	public boolean visit (IResourceProxy proxy) {
              + * 		//	 your code here
              + * 		return true;
              + * 	}
              + * }
              + * ResourcesPlugin.getWorkspace().getRoot().accept(new Visitor(), IResource.NONE);
              + * 
              + *

              + *

              + * Clients may implement this interface. + *

              + * + * @see IResource#accept(IResourceVisitor) + * @since 2.1 + */ +public interface IResourceProxyVisitor { + /** + * Visits the given resource. + * + * @param proxy for requesting information about the resource being visited; + * this object is only valid for the duration of the invocation of this + * method, and must not be used after this method has completed + * @return true if the resource's members should + * be visited; false if they should be skipped + * @exception CoreException if the visit fails for some reason. + */ + public boolean visit(IResourceProxy proxy) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java new file mode 100644 index 0000000000..149ee99b3b --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * 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 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.runtime.ICoreRunnable; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * A resource rule factory returns scheduling rules for API methods + * that modify the workspace. These rules can be used when creating jobs + * or other operations that perform a series of modifications on the workspace. + * This allows clients to implement two phase commit semantics, where all + * necessary rules are obtained prior to executing a long running operation. + *

              + * Note that simple use of the workspace APIs does not require use of scheduling + * rules. All workspace API methods that modify the workspace will automatically + * obtain any scheduling rules needed to perform the modification. However, if you + * are aggregating a set of changes to the workspace using WorkspaceJob + * or ICoreRunnable you can use scheduling rules to lock a + * portion of the workspace for the duration of the job or runnable. If you + * provide a non-null scheduling rule, a runtime exception will occur if you try to + * modify a portion of the workspace that is not covered by the rule for the runnable or job. + *

              + * If more than one rule is needed, they can be aggregated using the + * MultiRule.combine method. Simplifying a group of rules does not change + * the set of resources that are covered, but can improve job scheduling performance. + *

              + * Note that null is a valid scheduling rule (indicating that no + * resources need to be locked), and thus all methods in this class may + * return null. + * + * @see WorkspaceJob + * @see IWorkspace#run(ICoreRunnable, ISchedulingRule, int, org.eclipse.core.runtime.IProgressMonitor) + * @see org.eclipse.core.runtime.jobs.MultiRule#combine(ISchedulingRule, ISchedulingRule) + * @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 IResourceRuleFactory { + /** + * Returns the scheduling rule that is required for creating a project, folder, + * or file. + * + * @param resource the resource being created + * @return a scheduling rule, or null + */ + public ISchedulingRule createRule(IResource resource); + + /** + * Returns the scheduling rule that is required for building a project or the + * entire workspace. + * + * @return a scheduling rule, or null + */ + public ISchedulingRule buildRule(); + + /** + * Returns the scheduling rule that is required for changing the charset + * setting for a file or the default charset setting for a container. + * + * @param resource the resource for which the charset will be changed + * @return a scheduling rule, or null + * @since 3.1 + */ + public ISchedulingRule charsetRule(IResource resource); + + /** + * Returns the scheduling rule that is required for changing the derived flag + * on a resource. + * + * @param resource the resource for which the derived flag will be changed + * @return a scheduling rule, or null + * @since 3.6 + */ + public ISchedulingRule derivedRule(IResource resource); + + /** + * Returns the scheduling rule that is required for copying a resource. + * + * @param source the source of the copy + * @param destination the destination of the copy + * @return a scheduling rule, or null + */ + public ISchedulingRule copyRule(IResource source, IResource destination); + + /** + * Returns the scheduling rule that is required for deleting a resource. + * + * @param resource the resource to be deleted + * @return a scheduling rule, or null + */ + public ISchedulingRule deleteRule(IResource resource); + + /** + * Returns the scheduling rule that is required for creating, modifying, or + * deleting markers on a resource. + * + * @param resource the resource owning the marker to be modified + * @return a scheduling rule, or null + */ + public ISchedulingRule markerRule(IResource resource); + + /** + * Returns the scheduling rule that is required for maintaining sync + * info of a resource. + * + * @see ISynchronizer#setSyncInfo(QualifiedName, IResource, byte[]) + * @param resource the resource related to the sync info + * @return a scheduling rule, or null + * @since 3.12.100 + */ + public ISchedulingRule syncInfoRule(IResource resource); + + /** + * Returns the scheduling rule that is required for modifying a resource. + * For files, modification includes setting and appending contents. For + * projects, modification includes opening or closing the project, or + * setting the project description using the + * {@link IResource#AVOID_NATURE_CONFIG} flag. For all resources + * touch is considered to be a modification. + * + * @param resource the resource being modified + * @return a scheduling rule, or null + */ + public ISchedulingRule modifyRule(IResource resource); + + /** + * Returns the scheduling rule that is required for moving a resource. + * + * @param source the source of the move + * @param destination the destination of the move + * @return a scheduling rule, or null + */ + public ISchedulingRule moveRule(IResource source, IResource destination); + + /** + * Returns the scheduling rule that is required for performing + * refreshLocal on a resource. + * + * @param resource the resource to refresh + * @return a scheduling rule, or null + */ + public ISchedulingRule refreshRule(IResource resource); + + /** + * Returns the scheduling rule that is required for a validateEdit + * + * @param resources the resources to be validated + * @return a scheduling rule, or null + */ + public ISchedulingRule validateEditRule(IResource[] resources); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java new file mode 100644 index 0000000000..1ec95112ed --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java @@ -0,0 +1,322 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.runtime.*; + +/** + * Represents status related to resources in the Resources plug-in and + * defines the relevant status code constants. + * Status objects created by the Resources plug-in bear its unique id + * (ResourcesPlugin.PI_RESOURCES) and one of + * these status codes. + * + * @see org.eclipse.core.runtime.IStatus + * @see ResourcesPlugin#PI_RESOURCES + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceStatus extends IStatus { + + /* + * Status code definitions + */ + + // General constants [0-98] + // Information Only [0-32] + // Warnings [33-65] + /** Status code constant (value 35) indicating that a given + * nature set does not satisfy its constraints. + * Severity: warning. Category: general. + */ + public static final int INVALID_NATURE_SET = 35; + + // Errors [66-98] + + /** Status code constant (value 75) indicating that a builder failed. + * Severity: error. Category: general. + */ + public static final int BUILD_FAILED = 75; + + /** Status code constant (value 76) indicating that an operation failed. + * Severity: error. Category: general. + */ + public static final int OPERATION_FAILED = 76; + + /** Status code constant (value 77) indicating an invalid value. + * Severity: error. Category: general. + */ + public static final int INVALID_VALUE = 77; + + // Local file system constants [200-298] + // Information Only [200-232] + + // Warnings [233-265] + + /** Status code constant (value 234) indicating that a project + * description file (.project), was missing but it has been repaired. + * Severity: warning. Category: local file system. + */ + public static final int MISSING_DESCRIPTION_REPAIRED = 234; + + /** Status code constant (value 235) indicating the local file system location + * for a resource overlaps the location of another resource. + * Severity: warning. Category: local file system. + */ + public static final int OVERLAPPING_LOCATION = 235; + + // Errors [266-298] + + /** Status code constant (value 268) indicating a resource unexpectedly + * exists on the local file system. + * Severity: error. Category: local file system. + */ + public static final int EXISTS_LOCAL = 268; + + /** Status code constant (value 269) indicating a resource unexpectedly + * does not exist on the local file system. + * Severity: error. Category: local file system. + */ + public static final int NOT_FOUND_LOCAL = 269; + + /** Status code constant (value 270) indicating the local file system location for + * a resource could not be computed. + * Severity: error. Category: local file system. + */ + public static final int NO_LOCATION_LOCAL = 270; + + /** Status code constant (value 271) indicating an error occurred while + * reading part of a resource from the local file system. + * Severity: error. Category: local file system. + */ + public static final int FAILED_READ_LOCAL = 271; + + /** Status code constant (value 272) indicating an error occurred while + * writing part of a resource to the local file system. + * Severity: error. Category: local file system. + */ + public static final int FAILED_WRITE_LOCAL = 272; + + /** Status code constant (value 273) indicating an error occurred while + * deleting a resource from the local file system. + * Severity: error. Category: local file system. + */ + public static final int FAILED_DELETE_LOCAL = 273; + + /** Status code constant (value 274) indicating the workspace view of + * the resource differs from that of the local file system. The requested + * operation has been aborted to prevent the possible loss of data. + * Severity: error. Category: local file system. + */ + public static final int OUT_OF_SYNC_LOCAL = 274; + + /** Status code constant (value 275) indicating this file system is not case + * sensitive and a resource that differs only in case unexpectedly exists on + * the local file system. + * Severity: error. Category: local file system. + */ + public static final int CASE_VARIANT_EXISTS = 275; + + /** Status code constant (value 276) indicating a file exists in the + * file system but is not of the expected type (file instead of directory, + * or vice-versa). + * Severity: error. Category: local file system. + */ + public static final int WRONG_TYPE_LOCAL = 276; + + /** Status code constant (value 277) indicating that the parent + * file in the file system is marked as read-only. + * Severity: error. Category: local file system. + * @since 2.1 + */ + public static final int PARENT_READ_ONLY = 277; + + /** Status code constant (value 278) indicating a file exists in the + * file system but its name is not a valid resource name. + * Severity: error. Category: local file system. + */ + public static final int INVALID_RESOURCE_NAME = 278; + + /** Status code constant (value 279) indicating that the + * file in the file system is marked as read-only. + * Severity: error. Category: local file system. + * @since 3.0 + */ + public static final int READ_ONLY_LOCAL = 279; + + // Workspace constants [300-398] + // Information Only [300-332] + + // Warnings [333-365] + + /** Status code constant (value 333) indicating that a workspace path + * variable unexpectedly does not exist. + * Severity: warning. Category: workspace. + * @since 2.1 + */ + public static final int VARIABLE_NOT_DEFINED_WARNING = 333; + + // Errors [366-398] + + /** Status code constant (value 366) indicating a resource exists in the + * workspace but is not of the expected type. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_WRONG_TYPE = 366; + + /** Status code constant (value 367) indicating a resource unexpectedly + * exists in the workspace. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_EXISTS = 367; + + /** Status code constant (value 368) indicating a resource unexpectedly + * does not exist in the workspace. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_NOT_FOUND = 368; + + /** Status code constant (value 369) indicating a resource unexpectedly + * does not have content local to the workspace. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_NOT_LOCAL = 369; + + /** Status code constant (value 370) indicating a workspace + * is unexpectedly closed. + * Severity: error. Category: workspace. + */ + public static final int WORKSPACE_NOT_OPEN = 370; + + /** Status code constant (value 372) indicating a project is + * unexpectedly closed. + * Severity: error. Category: workspace. + */ + public static final int PROJECT_NOT_OPEN = 372; + + /** Status code constant (value 374) indicating that the path + * of a resource being created is occupied by an existing resource + * of a different type. + * Severity: error. Category: workspace. + */ + public static final int PATH_OCCUPIED = 374; + + /** Status code constant (value 375) indicating that the sync partner + * is not registered with the workspace synchronizer. + * Severity: error. Category: workspace. + */ + public static final int PARTNER_NOT_REGISTERED = 375; + + /** Status code constant (value 376) indicating a marker unexpectedly + * does not exist in the workspace tree. + * Severity: error. Category: workspace. + */ + public static final int MARKER_NOT_FOUND = 376; + + /** Status code constant (value 377) indicating a resource is + * unexpectedly not a linked resource. + * Severity: error. Category: workspace. + * @since 2.1 + */ + public static final int RESOURCE_NOT_LINKED = 377; + + /** Status code constant (value 378) indicating that linking is + * not permitted on a certain project. + * Severity: error. Category: workspace. + * @since 2.1 + */ + public static final int LINKING_NOT_ALLOWED = 378; + + /** Status code constant (value 379) indicating that a workspace path + * variable unexpectedly does not exist. + * Severity: error. Category: workspace. + * @since 2.1 + */ + public static final int VARIABLE_NOT_DEFINED = 379; + + /** Status code constant (value 380) indicating that an attempt was made to modify + * the workspace while it was locked. Resource changes are disallowed + * during certain types of resource change event notification. + * Severity: error. Category: workspace. + * @see IResourceChangeEvent + * @since 2.1 + */ + public static final int WORKSPACE_LOCKED = 380; + + /** Status code constant (value 381) indicating that a problem occurred while + * retrieving the content description for a resource. + * Severity: error. Category: workspace. + * @see IFile#getContentDescription + * @since 3.0 + */ + public static final int FAILED_DESCRIBING_CONTENTS = 381; + + /** Status code constant (value 382) indicating that a problem occurred while + * setting the charset for a resource. + * Severity: error. Category: workspace. + * @see IContainer#setDefaultCharset(String, IProgressMonitor) + * @see IFile#setCharset(String, IProgressMonitor) + * @since 3.0 + */ + public static final int FAILED_SETTING_CHARSET = 382; + + /** Status code constant (value 383) indicating that a problem occurred while + * getting the charset for a resource. + * Severity: error. Category: workspace. + * @since 3.0 + */ + public static final int FAILED_GETTING_CHARSET = 383; + + /** Status code constant (value 384) indicating a build configuration with + * the specified ID unexpectedly does not exist. + * Severity: error. Category: workspace. + * @since 3.7 + */ + public static final int BUILD_CONFIGURATION_NOT_FOUND = 384; + + // Internal constants [500-598] + // Information Only [500-532] + + // Warnings [533-565] + + // Errors [566-598] + + /** Status code constant (value 566) indicating an error internal to the + * platform has occurred. + * Severity: error. Category: internal. + */ + public static final int INTERNAL_ERROR = 566; + + /** Status code constant (value 567) indicating the platform could not read + * some of its metadata. + * Severity: error. Category: internal. + */ + public static final int FAILED_READ_METADATA = 567; + + /** Status code constant (value 568) indicating the platform could not write + * some of its metadata. + * Severity: error. Category: internal. + */ + public static final int FAILED_WRITE_METADATA = 568; + + /** Status code constant (value 569) indicating the platform could not delete + * some of its metadata. + * Severity: error. Category: internal. + */ + public static final int FAILED_DELETE_METADATA = 569; + + /** + * Returns the path of the resource associated with this status. + * + * @return the path of the resource related to this status + */ + public IPath getPath(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java new file mode 100644 index 0000000000..ee43d864e0 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * This interface is implemented by objects that visit resource trees. + *

              + * Usage: + *

              + * class Visitor implements IResourceVisitor {
              + *    public boolean visit(IResource res) {
              + *       // your code here
              + *       return true;
              + *    }
              + * }
              + * IResource root = ...;
              + * root.accept(new Visitor());
              + * 
              + *

              + *

              + * Clients may implement this interface. + *

              + * + * @see IResource#accept(IResourceVisitor) + */ +public interface IResourceVisitor { + /** + * Visits the given resource. + * + * @param resource the resource to visit + * @return true if the resource's members should + * be visited; false if they should be skipped + * @exception CoreException if the visit fails for some reason. + */ + public boolean visit(IResource resource) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java new file mode 100644 index 0000000000..0849d7d1fe --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java @@ -0,0 +1,197 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; + +/** + * A context for workspace save operations. + *

              + * Note that IWorkspace.save uses a + * different save context for each registered participant, + * allowing each to declare whether they have actively + * participated and decide whether to receive a resource + * delta on reactivation. + *

              + * + * @see IWorkspace#save(boolean, org.eclipse.core.runtime.IProgressMonitor) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ISaveContext { + + /*==================================================================== + * Constants related to save kind + *====================================================================*/ + + /** + * Type constant which identifies a full save. + * + * @see ISaveContext#getKind() + */ + public static final int FULL_SAVE = 1; + + /** + * Type constant which identifies a snapshot. + * + * @see ISaveContext#getKind() + */ + public static final int SNAPSHOT = 2; + + /** + * Type constant which identifies a project save. + * + * @see ISaveContext#getKind() + */ + public static final int PROJECT_SAVE = 3; + + /** + * Returns current files mapped with the ISaveContext.map + * facility or an empty array if there are no mapped files. + * + * @return the files currently mapped by the participant + * + * @see #map(IPath, IPath) + */ + public IPath[] getFiles(); + + /** + * Returns the type of this save. The types can be: + *
                + *
              • ISaveContext.FULL_SAVE
              • + *
              • ISaveContext.SNAPSHOT
              • + *
              • ISaveContext.PROJECT_SAVE
              • + *
              + * + * @return the type of the current save + */ + public int getKind(); + + /** + * Returns the number for the previous save in + * which the plug-in actively participated, or 0 + * if the plug-in has never actively participated in a save before. + *

              + * In the event of an unsuccessful save, this is the value to + * rollback to. + *

              + * + * @return the previous save number if positive, or 0 + * if never saved before + * @see ISaveParticipant#rollback(ISaveContext) + */ + public int getPreviousSaveNumber(); + + /** + * If the current save is a project save, this method returns the project + * being saved. + * + * @return the project being saved or null if this is not + * project save + * + * @see #getKind() + */ + public IProject getProject(); + + /** + * Returns the number for this save. This number is + * guaranteed to be 1 more than the + * previous save number. + *

              + * This is the value to use when, for example, creating files + * in which a participant will save its data. + *

              + * + * @return the save number + * @see ISaveParticipant#saving(ISaveContext) + */ + public int getSaveNumber(); + + /** + * Returns the current location for the given file or + * null if none. + * + * @return the location of a given file or null + * @see #map(IPath, IPath) + * @see ISavedState#lookup(IPath) + */ + public IPath lookup(IPath file); + + /** + * Maps the given plug-in file to its real location. This method is intended to be used + * with ISaveContext.getSaveNumber() to map plug-in configuration + * file names to real locations. + *

              + * For example, assume a plug-in has a configuration file named "config.properties". + * The map facility can be used to map that logical name onto a real + * name which is specific to a particular save (e.g., 10.config.properties, + * where 10 is the current save number). The paths specified here should + * always be relative to the plug-in state location for the plug-in saving the state. + *

              + *

              + * Each save participant must manage the deletion of its old state files. Old state files + * can be discovered using getPreviousSaveNumber or by using + * getFiles to discover the current files and comparing that to the + * list of files on disk. + *

              + * @param file the logical name of the participant's data file + * @param location the real (i.e., filesystem) name by which the file should be known + * for this save, or null to remove the entry + * @see #lookup(IPath) + * @see #getSaveNumber() + * @see #needSaveNumber() + * @see ISavedState#lookup(IPath) + */ + public void map(IPath file, IPath location); + + /** + * Indicates that the saved workspace tree should be remembered so that a delta + * will be available in a subsequent session when the plug-in re-registers + * to participate in saves. If this method is not called, no resource delta will + * be made available. This facility is not available for marker deltas. + * Plug-ins must assume that all markers may have changed when they are activated. + *

              + * Note that this is orthogonal to needSaveNumber. That is, + * one can ask for a delta regardless of whether or not one is an active participant. + *

              + *

              + * Note that deltas are not guaranteed to be saved even if saving is requested. + * Deltas cannot be supplied where the previous state is too old or has become invalid. + *

              + *

              + * This method is only valid for full saves. It is ignored during snapshots + * or project saves. + *

              + * + * @see IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant) + * @see ISavedState#processResourceChangeEvents(IResourceChangeListener) + */ + public void needDelta(); + + /** + * Indicates that this participant has actively participated in this save. + * If the save is successful, the current save number will be remembered; + * this save number will be the previous save number for subsequent saves + * until the participant again actively participates. + *

              + * If this method is not called, the plug-in is not deemed to be an active + * participant in this save. + *

              + *

              + * Note that this is orthogonal to needDelta. That is, + * one can be an active participant whether or not one asks for a delta. + *

              + * + * @see IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant) + * @see ISavedState#getSaveNumber() + */ + public void needSaveNumber(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java new file mode 100644 index 0000000000..e3ffa6c893 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.util.EventListener; +import org.eclipse.core.runtime.CoreException; + +/** + * A participant in the saving of the workspace. + *

              + * Plug-ins implement this interface and register to participate + * in workspace save operations. + *

              + *

              + * Clients may implement this interface. + *

              + * @see IWorkspace#save(boolean, org.eclipse.core.runtime.IProgressMonitor) + */ +public interface ISaveParticipant extends EventListener { + /** + * Tells this participant that the workspace save operation is now + * complete and it is free to go about its normal business. + * Exceptions are not expected to be thrown at this point, so they + * should be handled internally. + *

              + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

              + * + * @param context the save context object + */ + public void doneSaving(ISaveContext context); + + /** + * Tells this participant that the workspace is about to be + * saved. In preparation, the participant is expected to suspend + * its normal operation until further notice. saving + * will be next, followed by either doneSaving + * or rollback depending on whether the workspace + * save was successful. + *

              + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

              + * + * @param context the save context object + * @exception CoreException if this method fails to snapshot + * the state of this workspace + */ + public void prepareToSave(ISaveContext context) throws CoreException; + + /** + * Tells this participant to rollback its important state. + * The context's previous state number indicates what it was prior + * to the failed save. + * Exceptions are not expected to be thrown at this point, so they + * should be handled internally. + *

              + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

              + * + * @param context the save context object + * @see ISaveContext#getPreviousSaveNumber() + */ + public void rollback(ISaveContext context); + + /** + * Tells this participant to save its important state because + * the workspace is being saved, as described in the supplied + * save context. + *

              + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

              + *

              + * The basic contract for this method is the same for full saves, + * snapshots and project saves: the participant must absolutely guarantee that any + * important user data it has gathered will not be irrecoverably lost + * in the event of a crash. The only difference is in the space-time + * tradeoffs that the participant should make. + *

                + *
              • Full saves: the participant is + * encouraged to save additional non-essential information that will aid + * it in retaining user state and configuration information and quickly getting + * back in sync with the state of the platform at a later point. + *
              • + *
              • Snapshots: the participant is discouraged from saving non-essential + * information that could be recomputed in the unlikely event of a crash. + * This lifecycle event will happen often and participant actions should take + * an absolute minimum of time. + *
              • + *
              • Project saves: the participant should only save project related data. + * It is discouraged from saving non-essential information that could be recomputed + * in the unlikely event of a crash. + *
              • + *
              + * For instance, the Java IDE gathers various user preferences and would want to + * make sure that the current settings are safe and sound after a + * save (if not saved immediately). + * The Java IDE would likely save computed image builder state on full saves, + * because this would allow the Java IDE to be restarted later and not + * have to recompile the world at that time. On the other hand, the Java + * IDE would not save the image builder state on a snapshot because + * that information is non-essential; in the unlikely event of a crash, + * the image should be rebuilt either from scratch or from the last saved + * state. + *

              + *

              + * The following snippet shows how a plug-in participant would write + * its important state to a file whose name is based on the save + * number for this save operation. + *

              +	 *     Plugin plugin = ...; // known
              +	 *     int saveNumber = context.getSaveNumber();
              +	 *     String saveFileName = "save-" + Integer.toString(saveNumber);
              +	 *     File f = plugin.getStateLocation().append(saveFileName).toFile();
              +	 *     plugin.writeImportantState(f);
              +	 *     context.map(new Path("save"), new Path(saveFileName));
              +	 *     context.needSaveNumber();
              +	 *     context.needDelta(); // optional
              +	 * 
              + * When the plug-in is reactivated in a subsequent workspace session, + * it needs to re-register to participate in workspace saves. When it + * does so, it is handed back key information about what state it had last + * saved. If it's interested, it can also ask for a resource delta + * describing all resource changes that have happened since then, if this + * information is still available. + * The following snippet shows what a participant plug-in would + * need to do if and when it is reactivated: + *
              +	 *     IWorkspace ws = ...; // known
              +	 *     Plugin plugin = ...; // known
              +	 *     ISaveParticipant saver = ...; // known
              +	 *     ISavedState ss = ws.addSaveParticipant(plugin, saver);
              +	 *     if (ss == null) {
              +	 *         // activate for very first time
              +	 *         plugin.buildState();
              +	 *     } else {
              +	 *         String saveFileName = ss.lookup(new Path("save"));
              +	 *         File f = plugin.getStateLocation().append(saveFileName).toFile();
              +	 *         plugin.readImportantState(f);
              +	 *         IResourceChangeListener listener = new IResourceChangeListener() {
              +	 *             public void resourceChanged(IResourceChangeEvent event) {
              +	 *                 IResourceDelta delta = event.getDelta();
              +	 *                 if (delta != null) {
              +	 *                     // fast reactivation using delta
              +	 *                     plugin.updateState(delta);
              +	 *                 } else {
              +	 *                     // slower reactivation without benefit of delta
              +	 *                     plugin.rebuildState();
              +	 *                 }
              +	 *         };
              +	 *         ss.processResourceChangeEvents(listener);
              +	 *     }
              +	 * 
              + *

              + * + * @param context the save context object + * @exception CoreException if this method fails + * @see ISaveContext#getSaveNumber() + */ + public void saving(ISaveContext context) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java new file mode 100644 index 0000000000..2830d1526b --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; + +/** + * A data structure returned by {@link IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant)} + * containing a save number and an optional resource delta. + * + * @see IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ISavedState { + /** + * Returns the files mapped with the {@link ISaveContext#map(IPath, IPath)} + * facility. Returns an empty array if there are no mapped files. + * + * @return the files currently mapped by the participant + * + * @see #lookup(IPath) + * @see ISaveContext#map(IPath, IPath) + */ + public IPath[] getFiles(); + + /** + * Returns the save number for the save participant. + * This is the save number of the last successful save in which the plug-in + * actively participated, or 0 if the plug-in has + * never actively participated in a successful save. + * + * @return the save number + */ + public int getSaveNumber(); + + /** + * Returns the mapped location associated with the given path + * or null if none. + * + * @return the mapped location of a given path + * @see #getFiles() + * @see ISaveContext#map(IPath, IPath) + */ + public IPath lookup(IPath file); + + /** + * Used to receive notification of changes that might have happened + * while this plug-in was not active. The listener receives notifications of changes to + * the workspace resource tree since the time this state was saved. After this + * method is run, the delta is forgotten. Subsequent calls to this method + * will have no effect. + *

              + * No notification is received in the following cases: + *

                + *
              • if a saved state was never recorded ({@link ISaveContext#needDelta()} + * was not called)
              • + *
              • a saved state has since been forgotten (using {@link IWorkspace#forgetSavedTree(String)})
              • + *
              • a saved state has been deemed too old or has become invalid
              • + *
              + *

              + * All clients should have a contingency plan in place in case + * a changes are not available (the case should be very similar + * to the first time a plug-in is activated, and only has the + * current state of the workspace to work from). + *

              + *

              + * The supplied event is of type {@link IResourceChangeEvent#POST_BUILD} + * and contains the delta detailing changes since this plug-in last participated + * in a save. This event object (and the resource delta within it) is valid only + * for the duration of the invocation of this method. + *

              + * + * @param listener the listener + * @see ISaveContext#needDelta() + * @see IResourceChangeListener + */ + public void processResourceChangeEvents(IResourceChangeListener listener); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java new file mode 100644 index 0000000000..58034e65bb --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.io.InputStream; +import org.eclipse.core.runtime.*; + +/** + * A storage object represents a set of bytes which can be accessed. + * These may be in the form of an IFile or IFileState + * or any other object supplied by user code. The main role of an IStorage + * is to provide a uniform API for access to, and presentation of, its content. + *

              + * Storage objects implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

              + * Clients may implement this interface. + *

              + *

              + */ +public interface IStorage extends IAdaptable { + /** + * Returns an open input stream on the contents of this storage. + * The caller is responsible for closing the stream when finished. + * + * @return an input stream containing the contents of this storage + * @exception CoreException if the contents of this storage could + * not be accessed. See any refinements for more information. + */ + public InputStream getContents() throws CoreException; + + /** + * Returns the full path of this storage. The returned value + * depends on the implementor/extender. A storage need not + * have a path. + * + * @return the path related to the data represented by this storage or + * null if none. + */ + public IPath getFullPath(); + + /** + * Returns the name of this storage. + * The name of a storage is synonymous with the last segment + * of its full path though if the storage does not have a path, + * it may still have a name. + * + * @return the name of the data represented by this storage, + * or null if this storage has no name + * @see #getFullPath() + */ + public String getName(); + + /** + * Returns whether this storage is read-only. + * + * @return true if this storage is read-only + */ + public boolean isReadOnly(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java new file mode 100644 index 0000000000..43582635e3 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.QualifiedName; + +/** + * A synchronizer which maintains a list of registered partners and, on behalf of + * each partner, it keeps resource level synchronization information + * (a byte array). Sync info is saved only when the workspace is saved. + * + * @see IWorkspace#getSynchronizer() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ISynchronizer { + /** + * Visits the given resource and its descendents with the specified visitor + * if sync information for the given sync partner is found on the resource. If + * sync information for the given sync partner is not found on the resource, + * still visit the children of the resource if the depth specifies to do so. + * + * @param partner the sync partner name + * @param start the parent resource to start the visitation + * @param visitor the visitor to use when visiting the resources + * @param depth the depth to which members of this resource should be + * visited. One of IResource.DEPTH_ZERO, IResource.DEPTH_ONE, + * or IResource.DEPTH_INFINITE. + * @exception CoreException if this operation fails. Reasons include: + *
                + *
              • The resource does not exist.
              • + *
              • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
              • + *
              + */ + public void accept(QualifiedName partner, IResource start, IResourceVisitor visitor, int depth) throws CoreException; + + /** + * Adds the named synchronization partner to this synchronizer's + * registry of partners. Once a partner's name has been registered, sync + * information can be set and retrieve on resources relative to the name. + * Adding a sync partner multiple times has no effect. + * + * @param partner the partner name to register + * @see #remove(QualifiedName) + */ + public void add(QualifiedName partner); + + /** + * Discards the named partner's synchronization information + * associated with the specified resource and its descendents to the + * specified depth. + * + * @param partner the sync partner name + * @param resource the resource + * @param depth the depth to which members of this resource should be + * visited. One of IResource.DEPTH_ZERO, IResource.DEPTH_ONE, + * or IResource.DEPTH_INFINITE. + * @exception CoreException if this operation fails. Reasons include: + *
                + *
              • The resource does not exist.
              • + *
              • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
              • + *
              + */ + public void flushSyncInfo(QualifiedName partner, IResource resource, int depth) throws CoreException; + + /** + * Returns a list of synchronization partner names currently registered + * with this synchronizer. Returns an empty array if there are no + * registered sync partners. + * + * @return a list of sync partner names + */ + public QualifiedName[] getPartners(); + + /** + * Returns the named sync partner's synchronization information for the given resource. + * Returns null if no information is found. + * + * @param partner the sync partner name + * @param resource the resource + * @return the synchronization information, or null if none + * @exception CoreException if this operation fails. Reasons include: + *
                + *
              • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
              • + *
              + */ + public byte[] getSyncInfo(QualifiedName partner, IResource resource) throws CoreException; + + /** + * Removes the named synchronization partner from this synchronizer's + * registry. Does nothing if the partner is not registered. + * This discards all sync information for the defunct partner. + * After a partner has been unregistered, sync information for it can no + * longer be stored on resources. + * + * @param partner the partner name to remove from the registry + * @see #add(QualifiedName) + */ + public void remove(QualifiedName partner); + + /** + * Sets the named sync partner's synchronization information for the given resource. + * If the given info is non-null and the resource neither exists + * nor is a phantom, this method creates a phantom resource to hang on to the info. + * If the given info is null, any sync info for the resource stored by the + * given sync partner is discarded; in some cases, this may result in the deletion + * of a phantom resource if there is no more sync info to maintain for that resource. + *

              + * Sync information is not stored on the workspace root. Attempts to set information + * on the root will be ignored. + *

              + * + * @param partner the sync partner name + * @param resource the resource + * @param info the synchronization information, or null + * @exception CoreException if this operation fails. Reasons include: + *
                + *
              • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
              • + *
              + */ + public void setSyncInfo(QualifiedName partner, IResource resource, byte[] info) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java new file mode 100644 index 0000000000..b5079cc460 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java @@ -0,0 +1,1764 @@ +/******************************************************************************* + * 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 + * Red Hat Incorporated - loadProjectDescription(InputStream) + * Broadcom Corporation - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.io.InputStream; +import java.net.URI; +import java.util.Map; +import org.eclipse.core.resources.team.FileModificationValidationContext; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * Workspaces are the basis for Eclipse Platform resource management. There is + * only one workspace per running platform. All resources exist in the context + * of this workspace. + *

              + * A workspace corresponds closely to discreet areas in the local file system. + * Each project in a workspace maps onto a specific area of the file system. The + * folders and files within a project map directly onto the corresponding + * directories and files in the file system. One sub-directory, the workspace + * metadata area, contains internal information about the workspace and its + * resources. This metadata area should be accessed only by the Platform or via + * Platform API calls. + *

              + *

              + * Workspaces add value over using the file system directly in that they allow + * for comprehensive change tracking (through IResourceDelta s), + * various forms of resource metadata (e.g., markers and properties) as well as + * support for managing application/tool state (e.g., saving and restoring). + *

              + *

              + * The workspace as a whole is thread safe and allows one writer concurrent with + * multiple readers. It also supports mechanisms for saving and snapshotting the + * current resource state. + *

              + *

              + * The workspace is provided by the Resources plug-in and is automatically + * created when that plug-in is activated. The default workspace data area + * (i.e., where its resources are stored) overlap exactly with the platform's + * data area. That is, by default, the workspace's projects are found directly + * in the platform's data area. Individual project locations can be specified + * explicitly. + *

              + *

              + * The workspace resource namespace is always case-sensitive and + * case-preserving. Thus the workspace allows multiple sibling resources to exist + * with names that differ only in case. The workspace also imposes no + * restrictions on valid characters in resource names, the length of resource names, + * or the size of resources on disk. In situations where one or more resources + * are stored in a file system that is not case-sensitive, or that imposes restrictions + * on resource names, any failure to store or retrieve those resources will + * be propagated back to the caller of workspace API. + *

              + *

              + * Workspaces implement the IAdaptable interface; extensions are + * managed by the platform's adapter manager. + *

              + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IWorkspace extends IAdaptable { + /** + * flag constant (bit mask value 1) indicating that resource change + * notifications should be avoided during the invocation of a compound + * resource changing operation. + * + * @see IWorkspace#run(ICoreRunnable, ISchedulingRule, int, IProgressMonitor) + * @since 3.0 + */ + public static final int AVOID_UPDATE = 1; + + /** + * Constant that can be passed to {@link #validateEdit(org.eclipse.core.resources.IFile[], Object)} + * to indicate that the caller does not have access to a UI context but would still + * like to have UI-based validation if possible. + * @since 3.3 + * @see #validateEdit(IFile[], Object) + */ + public static final Object VALIDATE_PROMPT = FileModificationValidationContext.VALIDATE_PROMPT; + + /** + * The name of the IWorkspace OSGi service (value "org.eclipse.core.resources.IWorkspace"). + * @since 3.5 + */ + public static final String SERVICE_NAME = IWorkspace.class.getName(); + + /** + * Adds the given listener for resource change events to this workspace. Has + * no effect if an identical listener is already registered. + *

              + * This method is equivalent to: + * + *

              +	 * addResourceChangeListener(listener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE);
              +	 * 
              + * + *

              + * + * @param listener the listener + * @see IResourceChangeListener + * @see IResourceChangeEvent + * @see #addResourceChangeListener(IResourceChangeListener, int) + * @see #removeResourceChangeListener(IResourceChangeListener) + */ + public void addResourceChangeListener(IResourceChangeListener listener); + + /** + * Adds the given listener for the specified resource change events to this + * workspace. Has no effect if an identical listener is already registered + * for these events. After completion of this method, the given listener + * will be registered for exactly the specified events. If they were + * previously registered for other events, they will be de-registered. + *

              + * Once registered, a listener starts receiving notification of changes to + * resources in the workspace. The resource deltas in the resource change + * event are rooted at the workspace root. Most resource change + * notifications occur well after the fact; the exception is + * pre-notification of impending project closures and deletions. The + * listener continues to receive notifications until it is replaced or + * removed. + *

              + *

              + * Listeners can listen for several types of event as defined in + * IResourceChangeEvent. Clients are free to register for + * any number of event types however if they register for more than one, it + * is their responsibility to ensure they correctly handle the case where + * the same resource change shows up in multiple notifications. Clients are + * guaranteed to receive only the events for which they are registered. + *

              + * + * @param listener the listener + * @param eventMask the bit-wise OR of all event types of interest to the + * listener + * @see IResourceChangeListener + * @see IResourceChangeEvent + * @see #removeResourceChangeListener(IResourceChangeListener) + */ + public void addResourceChangeListener(IResourceChangeListener listener, int eventMask); + + /** + * Registers the given plug-in's workspace save participant, and returns an + * object describing the workspace state at the time of the last save in + * which the plug-in participated. + *

              + * Once registered, the workspace save participant will actively participate + * in the saving of this workspace. + *

              + * + * @param plugin the plug-in + * @param participant the participant + * @return the last saved state in which the plug-in participated, or + * null if the plug-in has not participated before + * @exception CoreException if the method fails to add the participant. + * Reasons include: + *
                + *
              • The previous state could not be recovered.
              • + *
              + * @see ISaveParticipant + * @see #removeSaveParticipant(Plugin) + * @deprecated Use {@link #addSaveParticipant(String, ISaveParticipant)} instead + */ + @Deprecated + public ISavedState addSaveParticipant(Plugin plugin, ISaveParticipant participant) throws CoreException; + + /** + * Registers the given plug-in's workspace save participant, and returns an + * object describing the workspace state at the time of the last save in + * which the bundle participated. + *

              + * Once registered, the workspace save participant will actively participate + * in the saving of this workspace. + *

              + * + * @param pluginId the unique identifier of the plug-in + * @param participant the participant + * @return the last saved state in which the plug-in participated, or + * null if the plug-in has not participated before + * @exception CoreException if the method fails to add the participant. + * Reasons include: + *
                + *
              • The previous state could not be recovered.
              • + *
              + * @see ISaveParticipant + * @see #removeSaveParticipant(String) + * @since 3.6 + */ + public ISavedState addSaveParticipant(String pluginId, ISaveParticipant participant) throws CoreException; + + /** + * Builds all projects in this workspace. Projects are built in the order + * specified in this workspace's description. Projects not mentioned in the + * order or for which the order cannot be determined are built in an + * undefined order after all other projects have been built. If no order is + * specified, the workspace computes an order determined by project + * references. + *

              + * This method may change resources; these changes will be reported in a + * subsequent resource change event. + *

              + *

              + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

              + * + * @param kind the kind of build being requested. Valid values are + *
                + *
              • {@link IncrementalProjectBuilder#FULL_BUILD}- indicates a full build.
              • + *
              • {@link IncrementalProjectBuilder#INCREMENTAL_BUILD}- indicates a incremental build.
              • + *
              • {@link IncrementalProjectBuilder#CLEAN_BUILD}- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states.
              • + *
              + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic {@link IResourceStatus#BUILD_FAILED} + * code, but it could also be any other status code; it might + * also be a {@link MultiStatus}. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#build(IBuildConfiguration[], int, boolean, IProgressMonitor) + * @see IProject#build(int, IProgressMonitor) + * @see #computeProjectOrder(IProject[]) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @see IResourceRuleFactory#buildRule() + */ + public void build(int kind, IProgressMonitor monitor) throws CoreException; + + /** + * Build the build configurations specified in the passed in build configuration array. + *

              + * Build order is determined by the workspace description and the project build configuration + * reference graph. + *

              + *

              + * If buildReferences is true, build configurations reachable through the build configuration graph are + * built as part of this build invocation. + *

              + *

              + * This method may change resources; these changes will be reported in a + * subsequent resource change event. + *

              + *

              + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

              + * @param buildConfigs array of configurations to build + * @param kind the kind of build being requested. Valid values are + *
                + *
              • {@link IncrementalProjectBuilder#FULL_BUILD}- indicates a full build.
              • + *
              • {@link IncrementalProjectBuilder#INCREMENTAL_BUILD}- indicates a incremental build.
              • + *
              • {@link IncrementalProjectBuilder#CLEAN_BUILD}- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states.
              • + *
              + * @param buildReferences boolean indicating if references should be transitively built. + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic {@link IResourceStatus#BUILD_FAILED} + * code, but it could also be any other status code; it might + * also be a {@link MultiStatus}. + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * + * @see IProject#build(int, IProgressMonitor) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @see IResourceRuleFactory#buildRule() + * @since 3.7 + */ + public void build(IBuildConfiguration[] buildConfigs, int kind, boolean buildReferences, IProgressMonitor monitor) throws CoreException; + + /** + * Checkpoints the operation currently in progress. This method is used in + * the middle of a group of operations to force a background autobuild (if + * the build argument is true) and send an interim notification of resource + * change events. + *

              + * When invoked in the dynamic scope of a call to the + * IWorkspace.run method, this method reports a single + * resource change event describing the net effect of all changes done to + * resources since the last round of notifications. When the outermost + * run method eventually completes, it will do another + * autobuild (if enabled) and report the resource changes made after this + * call. + *

              + *

              + * This method has no effect if invoked outside the dynamic scope of a call + * to the IWorkspace.run method. + *

              + *

              + * This method should be used under controlled circumstance (e.g., to break + * up extremely long-running operations). + *

              + * + * @param build whether or not to run a build + * @see IWorkspace#run(ICoreRunnable, ISchedulingRule, int, IProgressMonitor) + */ + public void checkpoint(boolean build); + + /** + * Returns the prerequisite ordering of the given projects. The computation + * is done by interpreting the projects' active build configuration references + * as dependency relationships. + * For example if A references B and C, and C references B, this method, + * given the list A, B, C will return the order B, C, A. That is, projects + * with no dependencies are listed first. + *

              + * The return value is a two element array of project arrays. The first + * project array is the list of projects which could be sorted (as outlined + * above). The second element of the return value is an array of the + * projects which are ambiguously ordered (e.g., they are part of a cycle). + *

              + *

              + * Cycles and ambiguities are handled by elimination. Projects involved in + * cycles are simply cut out of the ordered list and returned in an + * undefined order. Closed and non-existent projects are ignored and do not + * appear in the returned value at all. + *

              + * + * @param projects the projects to order + * @return the projects in sorted order and a list of projects which could + * not be ordered + * @deprecated Replaced by IWorkspace.computeProjectOrder, + * which produces a more usable result when there are cycles in project + * reference graph. + */ + @Deprecated + public IProject[][] computePrerequisiteOrder(IProject[] projects); + + /** + * Data structure for holding the multi-part outcome of + * IWorkspace.computeProjectOrder. + *

              + * This class is not intended to be instantiated by clients. + *

              + * + * @see IWorkspace#computeProjectOrder(IProject[]) + * @since 2.1 + */ + public final class ProjectOrder { + /** + * Creates an instance with the given values. + *

              + * This class is not intended to be instantiated by clients. + *

              + * + * @param projects initial value of projects field + * @param hasCycles initial value of hasCycles field + * @param knots initial value of knots field + */ + public ProjectOrder(IProject[] projects, boolean hasCycles, IProject[][] knots) { + this.projects = projects; + this.hasCycles = hasCycles; + this.knots = knots; + } + + /** + * A list of projects ordered so as to honor the project reference, and + * build configuration reference, relationships between these projects + * wherever possible. + * The elements are a subset of the ones passed as the projects + * parameter to IWorkspace.computeProjectOrder, where + * inaccessible (closed or non-existent) projects have been omitted. + */ + public IProject[] projects; + /** + * Indicates whether any of the accessible projects in + * projects are involved in non-trivial cycles. + * true if the reference graph contains at least + * one cycle involving two or more of the projects in + * projects, and false if none of the + * projects in projects are involved in cycles. + */ + public boolean hasCycles; + /** + * A list of knots in the project reference graph. This list is empty if + * the project reference graph does not contain cycles. If the project + * reference graph contains cycles, each element is a knot of two or + * more accessible projects from projects that are + * involved in a cycle of mutually dependent references. + */ + public IProject[][] knots; + } + + /** + * Computes a total ordering of the given projects based on both static and + * dynamic project references. If an existing and open project P references + * another existing and open project Q also included in the list, then Q + * should come before P in the resulting ordering. Closed and non-existent + * projects are ignored, and will not appear in the result. References to + * non-existent or closed projects are also ignored, as are any + * self-references. The total ordering is always consistent with the global + * total ordering of all open projects in the workspace. + *

              + * When there are choices, the choice is made in a reasonably stable way. + * For example, given an arbitrary choice between two projects, the one with + * the lower collating project name is usually selected. + *

              + *

              + * When the project reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references + * P1, P4 references P3, and P2 and P3 reference each other, then exactly + * one of the relationships between P2 and P3 will have to be ignored. The + * outcome will be either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result + * also contains complete details of any cycles present. + *

              + *

              + * This method is time-consuming and should not be called unnecessarily. + * There are a very limited set of changes to a workspace that could affect + * the outcome: creating, renaming, or deleting a project; opening or + * closing a project; adding or removing a project reference. + *

              + * + * @param projects the projects to order + * @return result describing the project order + * @since 2.1 + */ + public ProjectOrder computeProjectOrder(IProject[] projects); + + /** + * Copies the given sibling resources so that they are located as members of + * the resource at the given path; the names of the copies are the same as + * the corresponding originals. + *

              + * This is a convenience method, fully equivalent to: + * + *

              +	 * copy(resources, destination, (force ? IResource.FORCE : IResource.NONE), monitor);
              +	 * 
              + * + *

              + *

              + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been added to the new parent. + *

              + *

              + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

              + * + * @param resources the resources to copy + * @param destination the destination container path + * @param force a flag controlling whether resources that are not in sync + * with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status object with code OK if there were no + * problems; otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to copy some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #copy(IResource[],IPath,int,IProgressMonitor) + */ + public IStatus copy(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Copies the given sibling resources so that they are located as members of + * the resource at the given path; the names of the copies are the same as + * the corresponding originals. + *

              + * This method can be expressed as a series of calls to + * IResource.copy(IPath,int,IProgressMonitor), with "best + * effort" semantics: + *

                + *
              • Resources are copied in the order specified, using the given update + * flags.
              • + *
              • Duplicate resources are only copied once.
              • + *
              • The method fails if the resources are not all siblings.
              • + *
              • The failure of an individual copy does not necessarily prevent the + * method from attempting to copy other resources.
              • + *
              • The method fails if there are projects among the resources.
              • + *
              • The method fails if the path of the resources is a prefix of the + * destination path.
              • + *
              • This method also fails if one or more of the individual resource + * copy steps fails.
              • + *
              + *

              + *

              + * After successful completion, corresponding new resources will now exist + * as members of the resource at the given path. + *

              + *

              + * The supplied path may be absolute or relative. Absolute paths fully + * specify the new location for the resource, including its project. + * Relative paths are considered to be relative to the container of the + * resources being copied. A trailing separator is ignored. + *

              + *

              + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been added to the new parent. + *

              + *

              + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

              + * + * @param resources the resources to copy + * @param destination the destination container path + * @param updateFlags bit-wise or of update flag constants + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status object with code OK if there were no + * problems; otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to copy some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. Reasons include: + *
                + *
              • One of the resources does not exist.
              • + *
              • The resources are not siblings.
              • + *
              • One of the resources, or one of its descendents, is not local.
              • + *
              • The resource corresponding to the destination path does not exist. + *
              • + *
              • The resource corresponding to the parent destination path is a + * closed project.
              • + *
              • A corresponding target resource does exist.
              • + *
              • A resource of a different type exists at the target path.
              • + *
              • One of the resources is a project.
              • + *
              • The path of one of the resources is a prefix of the destination + * path.
              • + *
              • One of the resources, or one of its descendents, is out of sync with + * the local file system and FORCE is not specified.
              • + *
              • Resource changes are disallowed during certain types of resource + * change event notification. See IResourceChangeEvent for + * more details.
              • + *
              + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#copy(IPath,int,IProgressMonitor) + * @see IResourceRuleFactory#copyRule(IResource, IResource) + * @since 2.0 + */ + public IStatus copy(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes the given resources. + *

              + * This is a convenience method, fully equivalent to: + * + *

              +	 * delete(resources, IResource.KEEP_HISTORY | (force ? IResource.FORCE : IResource.NONE), monitor);
              +	 * 
              + * + *

              + *

              + * This method changes resources; these changes will be reported in a + * subsequent resource change event. + *

              + *

              + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

              + * + * @param resources the resources to delete + * @param force a flag controlling whether resources that are not in sync + * with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to delete some resource. The + * status contained in the exception is a multi-status indicating where the + * individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #delete(IResource[],int,IProgressMonitor) + */ + public IStatus delete(IResource[] resources, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes the given resources. + *

              + * This method can be expressed as a series of calls to + * IResource.delete(int,IProgressMonitor). + *

              + *

              + * The semantics of multiple deletion are: + *

                + *
              • Resources are deleted in the order presented, using the given update + * flags.
              • + *
              • Resources that do not exist are ignored.
              • + *
              • An individual deletion fails if the resource still exists + * afterwards.
              • + *
              • The failure of an individual deletion does not prevent the method + * from attempting to delete other resources.
              • + *
              • This method fails if one or more of the individual resource + * deletions fails; that is, if at least one of the resources in the list + * still exists at the end of this method.
              • + *
              + *

              + *

              + * This method changes resources; these changes will be reported in a + * subsequent resource change event. + *

              + *

              + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

              + * + * @param resources the resources to delete + * @param updateFlags bit-wise or of update flag constants + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to delete some resource. The + * status contained in the exception is a multi-status indicating where the + * individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + * @see IResourceRuleFactory#deleteRule(IResource) + * @since 2.0 + */ + public IStatus delete(IResource[] resources, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Removes the given markers from the resources with which they are + * associated. Markers that do not exist are ignored. + *

              + * This method changes resources; these changes will be reported in a + * subsequent resource change event. + *

              + * + * @param markers the markers to remove + * @exception CoreException if this method fails. Reasons include: + *
                + *
              • Resource changes are disallowed during certain types of resource + * change event notification. See IResourceChangeEvent for + * more details.
              • + *
              + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void deleteMarkers(IMarker[] markers) throws CoreException; + + /** + * Forgets any resource tree being saved for the plug-in with the given + * name. If the plug-in id is null, all trees are forgotten. + *

              + * Clients should not call this method unless they have a reason to do so. A + * plug-in which uses ISaveContext.needDelta in the process + * of a save indicates that it would like to be fed the resource delta the + * next time it is reactivated. If a plug-in never gets reactivated (or if + * it fails to successfully register to participate in workspace saves), the + * workspace nevertheless retains the necessary information to generate the + * resource delta if asked. This method allows such a long term leak to be + * plugged. + *

              + * + * @param pluginId the unique identifier of the plug-in, or null + * @see ISaveContext#needDelta() + */ + public void forgetSavedTree(String pluginId); + + /** + * Returns all filter matcher descriptors known to this workspace. Returns an empty + * array if there are no installed filter matchers. + * + * @return the filter matcher descriptors known to this workspace + * @since 3.6 + */ + public IFilterMatcherDescriptor[] getFilterMatcherDescriptors(); + + /** + * Returns the filter descriptor with the given unique identifier, or + * null if there is no such filter. + * + * @param filterMatcherId the filter matcher extension identifier (e.g. + * "com.example.coolFilter"). + * @return the filter matcher descriptor, or null + * @since 3.6 + */ + public IFilterMatcherDescriptor getFilterMatcherDescriptor(String filterMatcherId); + + /** + * Returns all nature descriptors known to this workspace. Returns an empty + * array if there are no installed natures. + * + * @return the nature descriptors known to this workspace + * @since 2.0 + */ + public IProjectNatureDescriptor[] getNatureDescriptors(); + + /** + * Returns the nature descriptor with the given unique identifier, or + * null if there is no such nature. + * + * @param natureId the nature extension identifier (e.g. + * "com.example.coolNature"). + * @return the nature descriptor, or null + * @since 2.0 + */ + public IProjectNatureDescriptor getNatureDescriptor(String natureId); + + /** + * Finds all dangling project references in this workspace. Projects which + * are not open are ignored. Returns a map with one entry for each open + * project in the workspace that has at least one dangling project + * reference; the value of the entry is an array of projects which are + * referenced by that project but do not exist in the workspace. Returns an + * empty Map if there are no projects in the workspace. + * + * @return a map (key type: IProject, value type: + * IProject[]) from project to dangling project references + */ + public Map getDanglingReferences(); + + /** + * Returns the workspace description. This object is responsible for + * defining workspace preferences. The returned value is a modifiable copy + * but changes are not automatically applied to the workspace. In order to + * changes take effect, IWorkspace.setDescription needs to be + * called. The workspace description values are store in the preference + * store. + * + * @return the workspace description + * @see #setDescription(IWorkspaceDescription) + */ + public IWorkspaceDescription getDescription(); + + /** + * Returns the root resource of this workspace. + * + * @return the workspace root + */ + public IWorkspaceRoot getRoot(); + + /** + * Returns a factory for obtaining scheduling rules prior to modifying + * resources in the workspace. + * + * @see IResourceRuleFactory + * @return a resource rule factory + * @since 3.0 + */ + public IResourceRuleFactory getRuleFactory(); + + /** + * Returns the synchronizer for this workspace. + * + * @return the synchronizer + * @see ISynchronizer + */ + public ISynchronizer getSynchronizer(); + + /** + * Returns whether this workspace performs autobuilds. + * + * @return true if autobuilding is on, false + * otherwise + */ + public boolean isAutoBuilding(); + + /** + * Returns whether the workspace tree is currently locked. Resource changes + * are disallowed during certain types of resource change event + * notification. See IResourceChangeEvent for more details. + * + * @return boolean true if the workspace tree is locked, + * false otherwise + * @see IResourceChangeEvent + * @since 2.1 + */ + public boolean isTreeLocked(); + + /** + * Reads the project description file (".project") from the given location + * in the local file system. This object is useful for discovering the + * correct name for a project before importing it into the workspace. + *

              + * The returned value is writeable. + *

              + * + * @param projectDescriptionFile the path in the local file system of an + * existing project description file + * @return a new project description + * @exception CoreException if the operation failed. Reasons include: + *
                + *
              • The project description file does not exist.
              • + *
              • The file cannot be opened or read.
              • + *
              • The file cannot be parsed as a legal project description.
              • + * + * @see #newProjectDescription(String) + * @see IProject#getDescription() + * @since 2.0 + */ + public IProjectDescription loadProjectDescription(IPath projectDescriptionFile) throws CoreException; + + /** + * Reads the project description file (".project") from the given InputStream. + * This object will not attempt to set the location since the project may not + * have a valid location on the local file system. + * This object is useful for discovering the correct name for a project before + * importing it into the workspace. + *

                + * The returned value is writeable. + *

                + * + * @param projectDescriptionFile an InputStream pointing to an existing project + * description file + * @return a new project description + * @exception CoreException if the operation failed. Reasons include: + *
                  + *
                • The stream could not be read.
                • + *
                • The stream does not contain a legal project description.
                • + * + * @see #newProjectDescription(String) + * @see IProject#getDescription() + * @see IWorkspace#loadProjectDescription(IPath) + * @since 3.1 + */ + public IProjectDescription loadProjectDescription(InputStream projectDescriptionFile) throws CoreException; + + /** + * Moves the given sibling resources so that they are located as members of + * the resource at the given path; the names of the new members are the + * same. + *

                  + * This is a convenience method, fully equivalent to: + * + *

                  +	 * move(resources, destination, IResource.KEEP_HISTORY | (force ? IResource.FORCE : IResource.NONE), monitor);
                  +	 * 
                  + * + *

                  + *

                  + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been removed from their parent and that corresponding + * resources have been added to the new parent. Additional information + * provided with resource delta shows that these additions and removals are + * pairwise related. + *

                  + *

                  + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

                  + * + * @param resources the resources to move + * @param destination the destination container path + * @param force a flag controlling whether resources that are not in sync + * with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages. + * @exception CoreException if the method fails to move some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #move(IResource[],IPath,int,IProgressMonitor) + */ + public IStatus move(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Moves the given sibling resources so that they are located as members of + * the resource at the given path; the names of the new members are the + * same. + *

                  + * This method can be expressed as a series of calls to + * IResource.move, with "best effort" semantics: + *

                    + *
                  • Resources are moved in the order specified.
                  • + *
                  • Duplicate resources are only moved once.
                  • + *
                  • The force flag has the same meaning as it does on the + * corresponding single-resource method.
                  • + *
                  • The method fails if the resources are not all siblings.
                  • + *
                  • The method fails the path of any of the resources is a prefix of the + * destination path.
                  • + *
                  • The failure of an individual move does not necessarily prevent the + * method from attempting to move other resources.
                  • + *
                  • This method also fails if one or more of the individual resource + * moves fails; that is, if at least one of the resources in the list still + * exists at the end of this method.
                  • + *
                  • History is kept for moved files. When projects are moved, no history + * is kept
                  • + *
                  + *

                  + *

                  + * After successful completion, the resources and descendents will no longer + * exist; but corresponding new resources will now exist as members of the + * resource at the given path. + *

                  + *

                  + * The supplied path may be absolute or relative. Absolute paths fully + * specify the new location for the resource, including its project. + * Relative paths are considered to be relative to the container of the + * resources being moved. A trailing separator is ignored. + *

                  + *

                  + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been removed from their parent and that corresponding + * resources have been added to the new parent. Additional information + * provided with resource delta shows that these additions and removals are + * pairwise related. + *

                  + *

                  + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

                  + * + * @param resources the resources to move + * @param destination the destination container path + * @param updateFlags bit-wise or of update flag constants + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages. + * @exception CoreException if the method fails to move some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. Reasons include: + *
                    + *
                  • One of the resources does not exist.
                  • + *
                  • The resources are not siblings.
                  • + *
                  • One of the resources, or one of its descendents, is not local.
                  • + *
                  • The resource corresponding to the destination path does not exist. + *
                  • + *
                  • The resource corresponding to the parent destination path is a + * closed project.
                  • + *
                  • A corresponding target resource does exist.
                  • + *
                  • A resource of a different type exists at the target path.
                  • + *
                  • The path of one of the resources is a prefix of the destination + * path.
                  • + *
                  • One of the resources, or one of its descendents, is out of sync with + * the local file system and FORCE is false. + *
                  • + *
                  • Resource changes are disallowed during certain types of resource + * change event notification. See IResourceChangeEvent for + * more details.
                  • + *
                  + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(IPath,int,IProgressMonitor) + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @since 2.0 + */ + public IStatus move(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Returns a new build configuration for the project, with the given name. + * The name is a human readable unique name for the build configuration in the + * project. The project need not exist. + *

                  + * This API can be used to create {@link IBuildConfiguration}s that will be used as references + * to {@link IBuildConfiguration}s in other projects. These references are set using + * {@link IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[])} + * and may have a null configuration name which will resolve to the referenced + * project's active configuration when the reference is used. + *

                  + *

                  + * Build configuration do not become part of a project + * description until set using {@link IProjectDescription#setBuildConfigs(String[])}. + *

                  + * + * @param projectName the name of the project on which the configuration will exist + * @param configName the name of the new build configuration + * @return a build configuration + * @see IProjectDescription#setBuildConfigs(String[]) + * @see IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[]) + * @see IBuildConfiguration + * @since 3.7 + */ + public IBuildConfiguration newBuildConfig(String projectName, String configName); + + /** + * Creates and returns a new project description for a project with the + * given name. This object is useful when creating, moving or copying + * projects. + *

                  + * The project description is initialized to: + *

                    + *
                  • the given project name
                  • + *
                  • no references to other projects
                  • + *
                  • an empty build spec
                  • + *
                  • an empty comment
                  • + *
                  + *

                  + *

                  + * The returned value is writeable. + *

                  + * + * @param projectName the name of the project + * @return a new project description + * @see IProject#getDescription() + * @see IProject#create(IProjectDescription, IProgressMonitor) + * @see IResource#copy(IProjectDescription, int, IProgressMonitor) + * @see IProject#move(IProjectDescription, boolean, IProgressMonitor) + */ + public IProjectDescription newProjectDescription(String projectName); + + /** + * Removes the given resource change listener from this workspace. Has no + * effect if an identical listener is not registered. + * + * @param listener the listener + * @see IResourceChangeListener + * @see #addResourceChangeListener(IResourceChangeListener) + */ + public void removeResourceChangeListener(IResourceChangeListener listener); + + /** + * Removes the workspace save participant for the given plug-in from this + * workspace. If no such participant is registered, no action is taken. + *

                  + * Once removed, the workspace save participant no longer actively + * participates in any future saves of this workspace. + *

                  + * + * @param plugin the plug-in + * @see ISaveParticipant + * @see #addSaveParticipant(Plugin, ISaveParticipant) + * @deprecated Use {@link #removeSaveParticipant(String)} instead + */ + @Deprecated + public void removeSaveParticipant(Plugin plugin); + + /** + * Removes the workspace save participant for the given plug-in from this + * workspace. If no such participant is registered, no action is taken. + *

                  + * Once removed, the workspace save participant no longer actively + * participates in any future saves of this workspace. + *

                  + * + * @param pluginId the unique identifier of the plug-in + * @see ISaveParticipant + * @see #addSaveParticipant(String, ISaveParticipant) + * @since 3.6 + */ + public void removeSaveParticipant(String pluginId); + + /** + * Runs the given action as an atomic workspace operation. + *

                  + * After running a method that modifies resources in the workspace, + * registered listeners receive after-the-fact notification of what just + * transpired, in the form of a resource change event. This method allows + * clients to call a number of methods that modify resources and only have + * resource change event notifications reported at the end of the entire + * batch. + *

                  + *

                  + * If this method is called outside the dynamic scope of another such call, + * this method runs the action and then reports a single resource change + * event describing the net effect of all changes done to resources by the + * action. + *

                  + *

                  + * If this method is called in the dynamic scope of another such call, this + * method simply runs the action. + *

                  + *

                  + * The supplied scheduling rule is used to determine whether this operation + * can be run simultaneously with workspace changes in other threads. If the + * scheduling rule conflicts with another workspace change that is currently + * running, the calling thread will be blocked until that change completes. + * If the action attempts to make changes to the workspace that were not + * specified in the scheduling rule, it will fail. If no scheduling rule is + * supplied, there are no scheduling restrictions for this operation. + * If a non-null scheduling rule is supplied, this operation + * must always support cancelation in the case where this operation becomes + * blocked by a long running background operation. + *

                  + *

                  + * The AVOID_UPDATE flag controls whether periodic resource change + * notifications should occur during the scope of this call. If this flag is + * specified, and no other threads modify the workspace concurrently, then + * all resource change notifications will be deferred until the end of this + * call. If this flag is not specified, the platform may decide to broadcast + * periodic resource change notifications during the scope of this call. + *

                  + *

                  + * Flags other than AVOID_UPDATE are ignored. + *

                  + * + * @param action the action to perform + * @param rule the scheduling rule to use when running this operation, or + * null if there are no scheduling restrictions for this + * operation. + * @param flags bit-wise or of flag constants (only AVOID_UPDATE is relevant + * here) + * @param monitor a progress monitor, or null if progress + * reporting is not desired. + * @exception CoreException if the operation failed. + * @exception OperationCanceledException if the operation is canceled. If a + * non-null scheduling rule is supplied, cancelation can occur + * even if no progress monitor is provided. + * + * @see #AVOID_UPDATE + * @see IResourceRuleFactory + * @since 3.11 + */ + public void run(ICoreRunnable action, ISchedulingRule rule, int flags, IProgressMonitor monitor) throws CoreException; + + /** + * Runs the given action as an atomic workspace operation. + *

                  + * This is a convenience method, fully equivalent to: + * + *

                  +	 * workspace.run(action, workspace.getRoot(), IWorkspace.AVOID_UPDATE, monitor);
                  +	 * 
                  + * + *

                  + * + * @param action the action to perform + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the operation failed. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see #run(ICoreRunnable, ISchedulingRule, int, IProgressMonitor) + * @since 3.11 + */ + public void run(ICoreRunnable action, IProgressMonitor monitor) throws CoreException; + + /** + * Identical to {@link #run(ICoreRunnable, ISchedulingRule, int, IProgressMonitor)}. + * New code should use that method. + * @since 3.0 + */ + public void run(IWorkspaceRunnable action, ISchedulingRule rule, int flags, IProgressMonitor monitor) throws CoreException; + + /** + * Identical to {@link #run(ICoreRunnable, IProgressMonitor)}. + * New code should use that method. + */ + public void run(IWorkspaceRunnable action, IProgressMonitor monitor) throws CoreException; + + /** + * Saves this workspace's valuable state on disk. Consults with all + * registered plug-ins so that they can coordinate the saving of their + * persistent state as well. + *

                  + * The full parameter indicates whether a full save or a + * snapshot is being requested. Snapshots save the workspace information + * that is considered hard to be recomputed in the unlikely event of a + * crash. It includes parts of the workspace tree, workspace and projects + * descriptions, markers and sync information. Full saves are heavy weight + * operations which save the complete workspace state. + *

                  + *

                  + * To ensure that all outstanding changes to the workspace have been + * reported to interested parties prior to saving, a full save cannot be + * used within the dynamic scope of an IWorkspace.run + * invocation. Snapshots can be called any time and are interpreted by the + * workspace as a hint that a snapshot is required. The workspace will + * perform the snapshot when possible. Even as a hint, snapshots should only + * be called when necessary as they impact system performance. Although + * saving does not change the workspace per se, its execution is serialized + * like methods that write the workspace. + *

                  + *

                  + * The workspace is comprised of several different kinds of data with + * varying degrees of importance. The most important data, the resources + * themselves and their persistent properties, are written to disk + * immediately; other data are kept in volatile memory and only written to + * disk periodically; and other data are maintained in memory and never + * written out. The following table summarizes what gets saved when: + *

                    + *
                  • creating or deleting resource - immediately
                  • + *
                  • setting contents of file - immediately
                  • + *
                  • changes to project description - immediately
                  • + *
                  • session properties - never
                  • + *
                  • changes to persistent properties - immediately
                  • + *
                  • markers -save
                  • + *
                  • synchronizer info -save
                  • + *
                  • shape of the workspace resource tree -save
                  • + *
                  • list of active plug-ins - never
                  • + *
                  + * Resource-based plug-in also have data with varying degrees of importance. + * Each plug-in gets to decide the policy for protecting its data, either + * immediately, never, or at save time. For the latter, the + * plug-in coordinates its actions with the workspace (see + * ISaveParticipant for details). + *

                  + *

                  + * If the platform is shutdown (or crashes) after saving the workspace, any + * information written to disk by the last successful workspace + * save will be restored the next time the workspace is + * reopened for the next session. Naturally, information that is written to + * disk immediately will be as of the last time it was changed. + *

                  + *

                  + * The workspace provides a general mechanism for keeping concerned parties + * apprised of any and all changes to resources in the workspace ( + * IResourceChangeListener). It is even possible for a + * plug-in to find out about changes to resources that happen between + * workspace sessions (see IWorkspace.addSaveParticipant). + *

                  + *

                  + * At certain points during this method, the entire workspace resource tree + * must be locked to prevent resources from being changed (read access to + * resources is permitted). + *

                  + *

                  + * Implementation note: The execution sequence is as follows. + *

                    + *
                  • A long-term lock on the workspace is taken out to prevent further + * changes to workspace until the save is done.
                  • + *
                  • The list of saveable resource tree snapshots is initially empty. + *
                  • + *
                  • A different ISaveContext object is created for each + * registered workspace save participant plug-in, reflecting the kind of + * save (ISaveContext.getKind), the previous save number in + * which this plug-in actively participated, and the new save number (= + * previous save number plus 1).
                  • + *
                  • Each registered workspace save participant is sent + * prepareToSave(context), passing in its own context + * object. + *
                      + *
                    • Plug-in suspends all activities until further notice.
                    • + *
                    + * If prepareToSave fails (throws an exception), the problem + * is logged and the participant is marked as unstable.
                  • + *
                  • In dependent-before-prerequisite order, each registered workspace + * save participant is sent saving(context), passing in its + * own context object. + *
                      + *
                    • Plug-in decides whether it wants to actively participate in this + * save. The plug-in only needs to actively participate if some of its + * important state has changed since the last time it actively participated. + * If it does decide to actively participate, it writes its important state + * to a brand new file in its plug-in state area under a generated file name + * based on context.getStateNumber() and calls + * context.needStateNumber() to indicate that it has actively + * participated. If upon reactivation the plug-in will want a resource delta + * covering all changes between now and then, the plug-in should invoke + * context.needDelta() to request this now; otherwise, a + * resource delta for the intervening period will not be available on + * reactivation.
                    • + *
                    + * If saving fails (throws an exception), the problem is + * logged and the participant is marked as unstable.
                  • + *
                  • The plug-in save table contains an entry for each plug-in that has + * registered to participate in workspace saves at some time in the past + * (the list of plug-ins increases monotonically). Each entry records the + * save number of the last successful save in which that plug-in actively + * participated, and, optionally, a saved resource tree (conceptually, this + * is a complete tree; in practice, it is compressed into a special delta + * tree representation). A copy of the plug-in save table is made. Entries + * are created or modified for each registered plug-in to record the + * appropriate save number (either the previous save number, or the previous + * save number plus 1, depending on whether the participant was active and + * asked for a new number).
                  • + *
                  • The workspace tree, the modified copy of the plug-in save table, all + * markers, etc. and all saveable resource tree snapshots are written to + * disk as one atomic operation .
                  • + *
                  • The long-term lock on the workspace is released.
                  • + *
                  • If the atomic save succeeded: + *
                      + *
                    • The modified copy of the plug-in save table becomes the new plug-in + * save table.
                    • + *
                    • In prerequisite-before-dependent order, each registered workspace + * save participant is sent doneSaving(context), passing in + * its own context object. + *
                        + *
                      • Plug-in may perform clean up by deleting obsolete state files in its + * plug-in state area.
                      • + *
                      • Plug-in resumes its normal activities.
                      • + *
                      + * If doneSaving fails (throws an exception), the problem is + * logged and the participant is marked as unstable. (The state number in + * the save table is not rolled back just because of this instability.) + *
                    • + *
                    • The workspace save operation returns.
                    • + *
                    + *
                  • If it failed: + *
                      + *
                    • The workspace previous state is restored.
                    • + *
                    • In prerequisite-before-dependent order, each registered workspace + * save participant is sent rollback(context), passing in + * its own context object. + *
                        + *
                      • Plug-in may perform clean up by deleting newly-created but obsolete + * state file in its plug-in state area.
                      • + *
                      • Plug-in resumes its normal activities.
                      • + *
                      + * If rollback fails (throws an exception), the problem is + * logged and the participant is marked as unstable. (The state number in + * the save table is rolled back anyway.)
                    • + *
                    • The workspace save operation fails.
                    • + *
                    + *
                  • + *
                  + *

                  + *

                  + * After a full save, the platform can be shutdown. This will cause the + * Resources plug-in and all the other plug-ins to shutdown, without + * disturbing the saved workspace on disk. + *

                  + *

                  + * When the platform is later restarted, activating the Resources plug-in + * opens the saved workspace. This reads into memory the workspace's + * resource tree, plug-in save table, and saved resource tree snapshots + * (everything that was written to disk in the atomic operation above). + * Later, when a plug-in gets reactivated and registers to participate in + * workspace saves, it is handed back the info from its entry in the plug-in + * save table, if it has one. It gets back the number of the last save in + * which it actively participated and, possibly, a resource delta. + *

                  + *

                  + * The only source of long term garbage would come from a plug-in that never + * gets reactivated, or one that gets reactivated but fails to register for + * workspace saves. (There is no such problem with a plug-in that gets + * uninstalled; its easy enough to scrub its state areas and delete its + * entry in the plug-in save table.) + *

                  + * + * @param full true if this is a full save, and + * false if this is only a snapshot for protecting against + * crashes + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status that may contain warnings, such as the failure of an + * individual participant + * @exception CoreException if this method fails to save the state of this + * workspace. Reasons include: + *
                    + *
                  • The operation cannot be batched with others.
                  • + *
                  + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #addSaveParticipant(Plugin, ISaveParticipant) + */ + public IStatus save(boolean full, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the workspace description. Its values are stored in the preference + * store. + * + * @param description the new workspace description. + * @see #getDescription() + * @exception CoreException if the method fails. Reasons include: + *
                    + *
                  • There was a problem setting the workspace description.
                  • + *
                  + */ + public void setDescription(IWorkspaceDescription description) throws CoreException; + + /** + * Returns a copy of the given set of natures sorted in prerequisite order. + * For each nature, it is guaranteed that all of its prerequisites will + * precede it in the resulting array. + * + *

                  + * Natures that are missing from the install or are involved in a + * prerequisite cycle are sorted arbitrarily. Duplicate nature IDs are + * removed, so the returned array may be smaller than the original. + *

                  + * + * @param natureIds a valid set of nature extension identifiers + * @return the set of nature Ids sorted in prerequisite order + * @see #validateNatureSet(String[]) + * @since 2.0 + */ + public String[] sortNatureSet(String[] natureIds); + + /** + * Advises that the caller intends to modify the contents of the given files + * in the near future and asks whether modifying all these files would be + * reasonable. The files must all exist. This method is used to give the VCM + * component an opportunity to check out (or otherwise prepare) the files if + * required. (It is provided in this component rather than in the UI so that + * "core" (i.e., head-less) clients can use it. Similarly, it is located + * outside the VCM component for the convenience of clients that must also + * operate in configurations without VCM.) + *

                  + *

                  + * A client (such as an editor) should perform a validateEdit + * on a file whenever it finds itself in the following position: (a) the + * file is marked read-only, and (b) the client believes it likely (not + * necessarily certain) that it will modify the file's contents at some + * point. A case in point is an editor that has a buffer opened on a file. + * When the user starts to dirty the buffer, the editor should check to see + * whether the file is read-only. If it is, it should call + * validateEdit, and can reasonably expect this call, when + * successful, to cause the file to become read-write. An editor should also + * be sensitive to a file becoming read-only again even after a successful + * validateEdit (e.g., due to the user checking in the file + * in a different view); the editor should again call + * validateEdit if the file is read-only before attempting to + * save the contents of the file. + *

                  + *

                  + * By passing a UI context, the caller indicates that the VCM component may + * contact the user to help decide how best to proceed. If no UI context is + * provided, the VCM component will make its decision without additional + * interaction with the user. If OK is returned, the caller can safely + * assume that all of the given files haven been prepared for modification + * and that there is good reason to believe that + * IFile.setContents (or appendContents) + * would be successful on any of them. If the result is not OK, modifying + * the given files might not succeed for the reason(s) indicated. + *

                  + *

                  + * If a shell is passed in as the context, the VCM component may bring up a + * dialogs to query the user or report difficulties; the shell should be + * used to parent any such dialogs; the caller may safely assume that the + * reasons for failure will have been made clear to the user. If + * {@link IWorkspace#VALIDATE_PROMPT} is passed + * as the context, this indicates that the caller does not have access to + * a UI context but would still like the user to be prompted if required. + * If null is passed, the user should not be contacted; any + * failures should be reported via the result; the caller may chose to + * present these to the user however they see fit. The ideal implementation + * of this method is transactional; no files would be affected unless the + * go-ahead could be given. (In practice, there may be no feasible way to + * ensure such changes get done atomically.) + *

                  + *

                  + * The method calls FileModificationValidator.validateEdit + * for the file modification validator (if provided by the VCM plug-in). + * When there is no file modification validator, this method returns a + * status with an IResourceStatus.READ_ONLY_LOCAL code if one + * of the files is read-only, and a status with an IStatus.OK + * code otherwise. + *

                  + *

                  + * This method may be called from any thread. If the UI context is used, it + * is the responsibility of the implementor of + * FileModificationValidator.validateEdit to interact with + * the UI context in an appropriate thread. + *

                  + * + * @param files the files that are to be modified; these files must all + * exist in the workspace + * @param context either {@link IWorkspace#VALIDATE_PROMPT}, + * or the org.eclipse.swt.widgets.Shell that is + * to be used to parent any dialogs with the user, or null if + * there is no UI context (declared as an Object to avoid any + * direct references on the SWT component) + * @return a status object that is OK if things are fine, + * otherwise a status describing reasons why modifying the given files is not + * reasonable. A status with a severity of CANCEL is returned + * if the validation was canceled, indicating the edit should not proceed. + * @see IResourceRuleFactory#validateEditRule(IResource[]) + * @since 2.0 + */ + public IStatus validateEdit(IFile[] files, Object context); + + /** + * Validates that the given resource will not (or would not, if the resource + * doesn't exist in the workspace yet) be filtered out from the workspace by + * its parent resource filters. + *

                  + * Note that if the resource or its parent doesn't exist yet in the workspace, + * it is possible that it will still be effectively filtered out once the resource + * and/or its parent is created, even though this method doesn't report it. + * + * But if this method reports the resource as filtered, even though it, or its + * parent, doesn't exist in the workspace yet, it means that the resource will + * be filtered out by its parent resource filters once it exists in the workspace. + *

                  + *

                  + * This method will return a status with severity IStatus.ERROR + * if the resource will be filtered out - removed - out of the workspace by + * its parent resource filters. + *

                  + *

                  + * Note: linked resources and virtual folders are never filtered out by their + * parent resource filters. + * + * @param resource the resource to validate the location for + * @return a status object with code IStatus.OK if the given + * resource is not filtered by its parent resource filters, otherwise a status + * object with severity IStatus.ERROR indicating that it will + * @see IStatus#OK + * @since 3.6 + */ + public IStatus validateFiltered(IResource resource); + + /** + * Validates the given path as the location of the given resource on disk. + * The path must be either an absolute file system path, or a relative path + * whose first segment is the name of a defined workspace path variable. In + * addition to the restrictions for paths in general (see IPath. + * isValidPath), + * a link location must also obey the following rules: + *

                    + *
                  • must not overlap with the platform's metadata directory
                  • + *
                  • must not be the same as or a parent of the root directory of the + * project the linked resource is contained in
                  • + *
                  + *

                  + * This method also checks that the given resource can legally become a + * linked resource. This includes the following restrictions: + *

                    + *
                  • must have a project as its immediate parent
                  • + *
                  • project natures and the team hook may disallow linked resources on + * projects they are associated with
                  • + *
                  • the global workspace preference to disable linking, + * ResourcesPlugin.PREF_DISABLE_LINKING must not be set to + * "true"
                  • . + *
                  + *

                  + *

                  + * This method will return a status with severity IStatus.ERROR + * if the location does not obey the above rules. Also, this method will + * return a status with severity IStatus.WARNING if the + * location overlaps the location of any existing resource in the workspace. + *

                  + *

                  + * Note: this method does not consider whether files or directories exist in + * the file system at the specified path. + * + * @param resource the resource to validate the location for + * @param location the location of the linked resource contents on disk + * @return a status object with code IStatus.OK if the given + * location is valid as the linked resource location, otherwise a status + * object with severity IStatus.WARNING or + * IStatus.ERROR indicating what is wrong with the location + * @see IStatus#OK + * @see ResourcesPlugin#PREF_DISABLE_LINKING + * @since 2.1 + */ + public IStatus validateLinkLocation(IResource resource, IPath location); + + /** + * Validates the given {@link URI} as the location of the given resource on disk. + * The location must be either an absolute URI, or a relative URI + * whose first segment is the name of a defined workspace path variable. + * A link location must obey the following rules: + *

                    + *
                  • must not overlap with the platform's metadata directory
                  • + *
                  • must not be the same as or a parent of the root directory of the + * project the linked resource is contained in
                  • + *
                  + *

                  + * This method also checks that the given resource can legally become a + * linked resource. This includes the following restrictions: + *

                    + *
                  • must have a project as its immediate parent
                  • + *
                  • project natures and the team hook may disallow linked resources on + * projects they are associated with
                  • + *
                  • the global workspace preference to disable linking, + * ResourcesPlugin.PREF_DISABLE_LINKING must not be set to + * "true"
                  • . + *
                  + *

                  + *

                  + * This method will return a status with severity IStatus.ERROR + * if the location does not obey the above rules. Also, this method will + * return a status with severity IStatus.WARNING if the + * location overlaps the location of any existing resource in the workspace. + *

                  + *

                  + * Note: this method does not consider whether files or directories exist in + * the file system at the specified location. + * + * @param resource the resource to validate the location for + * @param location the location of the linked resource contents in some file system + * @return a status object with code IStatus.OK if the given + * location is valid as the linked resource location, otherwise a status + * object with severity IStatus.WARNING or + * IStatus.ERROR indicating what is wrong with the location + * @see IStatus#OK + * @see ResourcesPlugin#PREF_DISABLE_LINKING + * @since 3.2 + */ + public IStatus validateLinkLocationURI(IResource resource, URI location); + + /** + * Validates the given string as the name of a resource valid for one of the + * given types. + *

                  + * In addition to the basic restrictions on paths in general (see + * {@link IPath#isValidSegment(String)}), a resource name must also not + * contain any characters or substrings that are not valid on the file system + * on which workspace root is located. In addition, the names "." and ".." + * are reserved due to their special meaning in file system paths. + *

                  + *

                  + * This validation check is done automatically as a resource is created (but + * not when the resource handle is constructed); this means that any + * resource that exists can be safely assumed to have a valid name and path. + * Note that the name of the workspace root resource is inherently invalid. + *

                  + * + * @param segment the name segment to be checked + * @param typeMask bitwise-or of the resource type constants ( + * FILE,FOLDER,PROJECT or + * ROOT) indicating expected resource type(s) + * @return a status object with code IStatus.OK if the given + * string is valid as a resource name, otherwise a status object indicating + * what is wrong with the string + * @see IResource#PROJECT + * @see IResource#FOLDER + * @see IResource#FILE + * @see IStatus#OK + */ + public IStatus validateName(String segment, int typeMask); + + /** + * Validates that each of the given natures exists, and that all nature + * constraints are satisfied within the given set. + *

                  + * The following conditions apply to validation of a set of natures: + *

                    + *
                  • all natures in the set exist in the plug-in registry + *
                  • all prerequisites of each nature are present in the set + *
                  • there are no cycles in the prerequisite graph of the set + *
                  • there are no two natures in the set that specify one-of-nature + * inclusion in the same group. + *
                  • there are no two natures in the set with the same id + *
                  + *

                  + *

                  + * An empty nature set is always valid. + *

                  + * + * @param natureIds an array of nature extension identifiers + * @return a status object with code IStatus.OK if the given + * set of natures is valid, otherwise a status object indicating what is + * wrong with the set + * @since 2.0 + */ + public IStatus validateNatureSet(String[] natureIds); + + /** + * Validates the given string as a path for a resource of the given type(s). + *

                  + * In addition to the restrictions for paths in general (see + * IPath.isValidPath), a resource path should also obey the + * following rules: + *

                    + *
                  • a resource path should be an absolute path with no device id + *
                  • its segments should be valid names according to + * validateName + *
                  • a path for the workspace root must be the canonical root path + *
                  • a path for a project should have exactly 1 segment + *
                  • a path for a file or folder should have more than 1 segment + *
                  • the first segment should be a valid project name + *
                  • the second through penultimate segments should be valid folder names + *
                  • the last segment should be a valid name of the given type + *
                  + *

                  + *

                  + * Note: this method does not consider whether a resource at the specified + * path exists. + *

                  + *

                  + * This validation check is done automatically as a resource is created (but + * not when the resource handle is constructed); this means that any + * resource that exists can be safely assumed to have a valid name and path. + *

                  + * + * @param path the path string to be checked + * @param typeMask bitwise-or of the resource type constants ( + * FILE,FOLDER,PROJECT, or + * ROOT) indicating expected resource type(s) + * @return a status object with code IStatus.OK if the given + * path is valid as a resource path, otherwise a status object indicating + * what is wrong with the string + * @see IResource#PROJECT + * @see IResource#FOLDER + * @see IResource#FILE + * @see IStatus#OK + * @see IResourceStatus#getPath() + */ + public IStatus validatePath(String path, int typeMask); + + /** + * Validates the given path as the location of the given project on disk. + * The path must be either an absolute file system path, or a relative path + * whose first segment is the name of a defined workspace path variable. In + * addition to the restrictions for paths in general (see IPath. + * isValidPath), + * a location path should also obey the following rules: + *
                    + *
                  • must not be the same as another open or closed project
                  • + *
                  • must not occupy the default location for any project, whether existing or not
                  • + *
                  • must not be the same as or a parent of the platform's working directory
                  • + *
                  • must not be the same as or a child of the location of any existing + * linked resource in the given project
                  • + *
                  + *

                  + *

                  + * Note: this method does not consider whether files or directories exist in + * the file system at the specified path. + *

                  + * + * @param project the project to validate the location for, can be null + * if non default project location is validated + * @param location the location of the project contents on disk, or null + * if the default project location is used + * @return a status object with code IStatus.OK if the given + * location is valid as the project content location, otherwise a status + * object indicating what is wrong with the location + * @see IProjectDescription#getLocationURI() + * @see IProjectDescription#setLocation(IPath) + * @see IStatus#OK + */ + public IStatus validateProjectLocation(IProject project, IPath location); + + /** + * Validates the given URI as the location of the given project. + * The location must be either an absolute URI, or a relative URI + * whose first segment is the name of a defined workspace path variable. + * A project location must obey the following rules: + *
                    + *
                  • must not be the same as another open or closed project
                  • + *
                  • must not occupy the default location for any project, whether existing or not
                  • + *
                  • must not be the same as or a parent of the platform's working directory
                  • + *
                  • must not be the same as or a child of the location of any existing + * linked resource in the given project
                  • + *
                  + *

                  + *

                  + * Note: this method does not consider whether files or directories exist in + * the file system at the specified path. + *

                  + * + * @param project the project to validate the location for, can be null + * if non default project location is validated + * @param location the location of the project contents on disk, or null + * if the default project location is used + * @return a status object with code IStatus.OK if the given + * location is valid as the project content location, otherwise a status + * object indicating what is wrong with the location + * @see IProjectDescription#getLocationURI() + * @see IProjectDescription#setLocationURI(URI) + * @see IStatus#OK + * @since 3.2 + */ + public IStatus validateProjectLocationURI(IProject project, URI location); + + /** + * Returns the path variable manager for this workspace. + * + * @return the path variable manager + * @see IPathVariableManager + * @since 2.1 + */ + public IPathVariableManager getPathVariableManager(); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java new file mode 100644 index 0000000000..84a60cfd69 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +/** + * A workspace description represents the workspace preferences. It can be + * used to query the current preferences and set new ones. The workspace + * preference values are stored in the preference store and are also accessible + * via the preference mechanism. Constants for the preference keys are found + * on the ResourcesPlugin class. + * + * @see IWorkspace#getDescription() + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see IWorkspace#newProjectDescription(String) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IWorkspaceDescription { + /** + * Returns the order in which projects in the workspace should be built. + * The returned value is null if the workspace's default build + * order is being used. + * + * @return the names of projects in the order they will be built, + * or null if the default build order should be used + * @see #setBuildOrder(String[]) + * @see ResourcesPlugin#PREF_BUILD_ORDER + */ + public String[] getBuildOrder(); + + /** + * Returns the maximum length of time, in milliseconds, a file state should be + * kept in the local history. This setting is ignored by the workspace when + * isApplyFileStatePolicy() returns false. + * + * @return the maximum time a file state should be kept in the local history + * represented in milliseconds + * @see #setFileStateLongevity(long) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_FILE_STATE_LONGEVITY + */ + public long getFileStateLongevity(); + + /** + * Returns the maximum number of times that the workspace should rebuild when + * builders affect projects that have already been built. + * + * @return the maximum number of times that the workspace should rebuild when + * builders affect projects that have already been built. + * @see #setMaxBuildIterations(int) + * @see ResourcesPlugin#PREF_MAX_BUILD_ITERATIONS + * @since 2.1 + */ + public int getMaxBuildIterations(); + + /** + * Returns the maximum number of states per file that can be stored in the local history. + * This setting is ignored by the workspace when isApplyFileStatePolicy() + * returns false. + * + * @return the maximum number of states per file that can be stored in the local history + * @see #setMaxFileStates(int) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_MAX_FILE_STATES + */ + public int getMaxFileStates(); + + /** + * Returns the maximum permitted size of a file, in bytes, to be stored in the + * local history. This setting is ignored by the workspace when + * isApplyFileStatePolicy() returns false. + * + * @return the maximum permitted size of a file to be stored in the local history + * @see #setMaxFileStateSize(long) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_MAX_FILE_STATE_SIZE + */ + public long getMaxFileStateSize(); + + /** + * Returns whether file states are discarded according to the policy specified by + * setFileStateLongevity(long), setMaxFileStates(int) + * and setMaxFileStateSize(long) methods. + * + * @return true if file states are removed due to the policy, + * false otherwise + * @see #setApplyFileStatePolicy(boolean) + * @see #setFileStateLongevity(long) + * @see #setMaxFileStates(int) + * @see #setMaxFileStateSize(long) + * @see ResourcesPlugin#PREF_APPLY_FILE_STATE_POLICY + * @since 3.6 + */ + public boolean isApplyFileStatePolicy(); + + /** + * Returns the interval between automatic workspace snapshots. + * + * @return the amount of time in milliseconds between automatic workspace snapshots + * @see #setSnapshotInterval(long) + * @see ResourcesPlugin#PREF_SNAPSHOT_INTERVAL + * @since 2.0 + */ + public long getSnapshotInterval(); + + /** + * Returns whether this workspace performs autobuilds. + * + * @return true if autobuilding is on, otherwise + * false + * @see #setAutoBuilding(boolean) + * @see ResourcesPlugin#PREF_AUTO_BUILDING + */ + public boolean isAutoBuilding(); + + /** + * Records whether this workspace performs autobuilds. + *

                  + * When autobuild is on, any changes made to a project and its + * resources automatically triggers an incremental build of the workspace. + *

                  + *

                  + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                  + * + * @param value true to turn on autobuilding, + * and false to turn it off + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #isAutoBuilding() + * @see ResourcesPlugin#PREF_AUTO_BUILDING + */ + public void setAutoBuilding(boolean value); + + /** + * Sets the order in which projects in the workspace should be built. + * Projects not named in this list are built in a default order defined + * by the workspace. Set this value to null to use the + * default ordering for all projects. Projects not named in the list are + * built in unspecified order after all ordered projects. + *

                  + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                  + * + * @param value the names of projects in the order in which they are built, + * or null to use the workspace's default order for all projects + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getBuildOrder() + * @see ResourcesPlugin#PREF_BUILD_ORDER + */ + public void setBuildOrder(String[] value); + + /** + * Sets the maximum time, in milliseconds, a file state should be kept in the + * local history. This setting is ignored by the workspace when setApplyFileStatePolicy(boolean) + * is set to false. + *

                  + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                  + * + * @param time the maximum number of milliseconds a file state should be + * kept in the local history + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getFileStateLongevity() + * @see #setApplyFileStatePolicy(boolean) + * @see ResourcesPlugin#PREF_FILE_STATE_LONGEVITY + */ + public void setFileStateLongevity(long time); + + /** + * Sets the maximum number of times that the workspace should rebuild when + * builders affect projects that have already been built. + *

                  + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                  + * + * @param number the maximum number of times that the workspace should rebuild + * when builders affect projects that have already been built. + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getMaxBuildIterations() + * @see ResourcesPlugin#PREF_MAX_BUILD_ITERATIONS + * @since 2.1 + */ + public void setMaxBuildIterations(int number); + + /** + * Sets the maximum number of states per file that can be stored in the local history. + * If the maximum number is reached, older states are removed in favor of + * new ones. This setting is ignored by the workspace when setApplyFileStatePolicy(boolean) + * is set to false. + *

                  + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                  + * + * @param number the maximum number of states per file that can be stored in the local history + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getMaxFileStates() + * @see #setApplyFileStatePolicy(boolean) + * @see ResourcesPlugin#PREF_MAX_FILE_STATES + */ + public void setMaxFileStates(int number); + + /** + * Sets the maximum permitted size of a file, in bytes, to be stored in the + * local history. This setting is ignored by the workspace when setApplyFileStatePolicy(boolean) + * is set to false. + *

                  + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                  + * + * @param size the maximum permitted size of a file to be stored in the local history + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getMaxFileStateSize() + * @see #setApplyFileStatePolicy(boolean) + * @see ResourcesPlugin#PREF_MAX_FILE_STATE_SIZE + */ + public void setMaxFileStateSize(long size); + + /** + * Sets whether file states are discarded according to the policy specified by + * setFileStateLongevity(long), setMaxFileStates(int) + * and setMaxFileStateSize(long) methods. + *

                  + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                  + * + * @param apply true if file states are removed due to the policy, + * false otherwise + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #setFileStateLongevity(long) + * @see #setMaxFileStates(int) + * @see #setMaxFileStateSize(long) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_APPLY_FILE_STATE_POLICY + * @since 3.6 + */ + public void setApplyFileStatePolicy(boolean apply); + + /** + * Sets the interval between automatic workspace snapshots. The new interval + * will only take effect after the next snapshot. + *

                  + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                  + * + * @param delay the amount of time in milliseconds between automatic workspace snapshots + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getSnapshotInterval() + * @see ResourcesPlugin#PREF_SNAPSHOT_INTERVAL + * @since 2.0 + */ + public void setSnapshotInterval(long delay); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java new file mode 100644 index 0000000000..d11b6d0507 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java @@ -0,0 +1,402 @@ +/******************************************************************************* + * Copyright (c) 2000, 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.resources; + +import java.net.URI; +import org.eclipse.core.runtime.*; + +/** + * A root resource represents the top of the resource hierarchy in a workspace. + * There is exactly one root in a workspace. The root resource has the following + * behavior: + *
                    + *
                  • It cannot be moved or copied
                  • + *
                  • It always exists.
                  • + *
                  • Deleting the root deletes all of the children under the root but leaves the root itself
                  • + *
                  • It is always local.
                  • + *
                  • It is never a phantom.
                  • + *
                  + *

                  + * Workspace roots implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

                  + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IWorkspaceRoot extends IContainer, IAdaptable { + + /** + * Deletes everything in the workspace except the workspace root resource + * itself. + *

                  + * This is a convenience method, fully equivalent to: + *

                  +	 *   delete(
                  +	 *     (deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT )
                  +	 *        | (force ? FORCE : IResource.NONE),
                  +	 *     monitor);
                  +	 * 
                  + *

                  + *

                  + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

                  + *

                  + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                  + * + * @param deleteContent a flag controlling how whether content is + * aggressively deleted + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                    + *
                  • A project could not be deleted.
                  • + *
                  • A project's contents could not be deleted.
                  • + *
                  • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                  • + *
                  + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the handles to all the resources (workspace root, project, + * folder) in the workspace which are mapped to the given path in the local + * file system. Returns an empty array if there are none. + *

                  + * If the path maps to the platform working location, the returned object + * will be a single element array consisting of an object of type + * ROOT. + *

                  + *

                  + * If the path maps to a project, the resulting array will contain a + * resource of type PROJECT, along with any linked folders that + * share the same location. Otherwise the resulting array will contain + * folders (type FOLDER). + *

                  + *

                  + * The path should be absolute; a relative path will be treated as absolute. + * The path segments need not be valid names; a trailing separator is + * ignored. The resulting resources may not currently exist. + *

                  + *

                  + * The result will omit team private members and hidden resources. The + * result will omit resources within team private members or hidden + * containers. + *

                  + *

                  + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                  + * + * @param location + * a path in the local file system + * @return the corresponding containers in the workspace, or an empty array + * if none + * @since 2.1 + * @deprecated use {@link #findContainersForLocationURI(URI)} instead + */ + @Deprecated + public IContainer[] findContainersForLocation(IPath location); + + /** + * Returns the handles to all the resources (workspace root, project, + * folder) in the workspace which are mapped to the given URI. Returns an + * empty array if there are none. + *

                  + * If the path maps to the platform working location, the returned object + * will be a single element array consisting of an object of type + * ROOT. + *

                  + *

                  + * If the path maps to a project, the resulting array will contain a + * resource of type PROJECT, along with any linked folders that + * share the same location. Otherwise the resulting array will contain + * folders (type FOLDER). + *

                  + *

                  + * The URI must be absolute; its segments need not be valid names; a + * trailing separator is ignored. The resulting resources may not currently + * exist. + *

                  + *

                  + * The result will omit team private members and hidden resources. The + * result will omit resources within team private member sor hidden + * containers. + *

                  + *

                  + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                  + *

                  + * This is a convenience method, fully equivalent to + * findContainersForLocationURI(location, IResource.NONE). + *

                  + * + * @param location + * a URI path into some file system + * @return the corresponding containers in the workspace, or an empty array + * if none + * @since 3.2 + */ + public IContainer[] findContainersForLocationURI(URI location); + + /** + * Returns the handles to all the resources (workspace root, project, + * folder) in the workspace which are mapped to the given URI. Returns an + * empty array if there are none. + *

                  + * If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the + * member flags, team private members will be included along with the + * others. If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not + * specified (recommended), the result will omit any team private member + * resources. + *

                  + *

                  + * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, + * hidden members will be included along with the others. If the + * {@link #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 + * a URI path into some file system + * @param memberFlags + * bit-wise or of member flag constants ( + * {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} and {@link #INCLUDE_HIDDEN}) + * indicating which members are of interest + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 3.5 + */ + public IContainer[] findContainersForLocationURI(URI location, int memberFlags); + + /** + * Returns the handles of all files that are mapped to the given path in the + * local file system. Returns an empty array if there are none. The path + * should be absolute; a relative path will be treated as absolute. The path + * segments need not be valid names. The resulting files may not currently + * exist. + *

                  + * The result will omit any team private member and hidden resources. The + * result will omit resources within team private member or hidden + * containers. + *

                  + *

                  + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                  + * + * @param location + * a path in the local file system + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 2.1 + * @deprecated use {@link #findFilesForLocationURI(URI)} instead + */ + @Deprecated + public IFile[] findFilesForLocation(IPath location); + + /** + * Returns the handles of all files that are mapped to the given URI. + * Returns an empty array if there are none. The URI must be absolute; its + * path segments need not be valid names. The resulting files may not + * currently exist. + *

                  + * The result will omit any team private member and hidden resources. The + * result will omit resources within team private member or hidden + * containers. + *

                  + *

                  + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                  + *

                  + * This is a convenience method, fully equivalent to + * findFilesForLocationURI(location, IResource.NONE). + *

                  + * + * @param location + * a URI path into some file system + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 3.2 + */ + public IFile[] findFilesForLocationURI(URI location); + + /** + * Returns the handles of all files that are mapped to the given URI. + * Returns an empty array if there are none. The URI must be absolute; its + * path segments need not be valid names. The resulting files may not + * currently exist. + *

                  + * If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the + * member flags, team private members will be included along with the + * others. If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not + * specified (recommended), the result will omit any team private member + * resources. + *

                  + *

                  + * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, + * hidden members will be included along with the others. If the + * {@link #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 + * a URI path into some file system + * @param memberFlags + * bit-wise or of member flag constants ( + * {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} and {@link #INCLUDE_HIDDEN}) + * indicating which members are of interest + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 3.5 + */ + public IFile[] findFilesForLocationURI(URI location, int memberFlags); + + /** + * Returns a handle to the workspace root, project or folder + * which is mapped to the given path + * in the local file system, or null if none. + * If the path maps to the platform working location, the returned object + * will be of type ROOT. If the path maps to a + * project, the resulting object will be + * of type PROJECT; otherwise the resulting object + * will be a folder (type FOLDER). + * The path should be absolute; a relative path will be treated as + * absolute. The path segments need not be valid names; a trailing separator is ignored. + * The resulting resource may not currently exist. + *

                  + * This method returns null when the given file system location is not equal to + * or under the location of any existing project in the workspace, or equal to the + * location of the platform working location. + *

                  + *

                  + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                  + *

                  + * This method prefers a container whose path has a minimal number of segments. + * I.e. a container in a nested project is preferred over a container in an enclosing project. + *

                  + *

                  + * Warning: This method ignores linked resources and their children. Since + * linked resources may overlap other resources, a unique mapping from a + * file system location to a single resource is not guaranteed. To find all + * resources for a given location, including linked resources, use the method + * findContainersForLocation. + *

                  + * + * @param location a path in the local file system + * @return the corresponding project or folder in the workspace, + * or null if none + */ + public IContainer getContainerForLocation(IPath location); + + /** + * Returns a handle to the file which is mapped to the given path + * in the local file system, or null if none. + * The path should be absolute; a relative path will be treated as + * absolute. The path segments need not be valid names. + * The resulting file may not currently exist. + *

                  + * This method returns null when the given file system location is not under + * the location of any existing project in the workspace. + *

                  + *

                  + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                  + *

                  + * This method prefers a file whose path has a minimal number of segments. + * I.e. a file in a nested project is preferred over a file in an enclosing project. + *

                  + *

                  + * Warning: This method ignores linked resources and their children. Since + * linked resources may overlap other resources, a unique mapping from a + * file system location to a single resource is not guaranteed. To find all + * resources for a given location, including linked resources, use the method + * findFilesForLocation. + *

                  + * + * @param location a path in the local file system + * @return the corresponding file in the workspace, + * or null if none + */ + public IFile getFileForLocation(IPath location); + + /** + * Returns a handle to the project resource with the given name + * which is a child of this root. The given name must be a valid + * path segment as defined by {@link IPath#isValidSegment(String)}. + *

                  + * Note: This method deals exclusively with resource handles, + * independent of whether the resources exist in the workspace. + * With the exception of validating that the name is a valid path segment, + * validation checking of the project name is not done + * when the project handle is constructed; rather, it is done + * automatically as the project is created. + *

                  + * + * @param name the name of the project + * @return a project resource handle + * @see #getProjects() + */ + public IProject getProject(String name); + + /** + * Returns the collection of projects which exist under this root. + * The projects can be open or closed. + *

                  + * This is a convenience method, fully equivalent to getProjects(IResource.NONE). + * Hidden projects are not included. + *

                  + * @return an array of projects + * @see #getProject(String) + * @see IResource#isHidden() + */ + public IProject[] getProjects(); + + /** + * Returns the collection of projects which exist under this root. + * The projects can be open or closed. + *

                  + * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, hidden + * projects will be included along with the others. If the {@link #INCLUDE_HIDDEN} flag + * is not specified (recommended), the result will omit any hidden projects. + *

                  + * + * @param memberFlags bit-wise or of member flag constants indicating which + * projects are of interest (only {@link #INCLUDE_HIDDEN} is currently applicable) + * @return an array of projects + * @see #getProject(String) + * @see IResource#isHidden() + * @since 3.4 + */ + public IProject[] getProjects(int memberFlags); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java new file mode 100644 index 0000000000..513604abfe --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.runtime.*; + +/** + * This interface is structurally equivalent to {@link ICoreRunnable}. New code should use + * {@link ICoreRunnable} instead of {@code IWorkspaceRunnable}. + *

                  + * Clients may implement this interface. + *

                  + * @see IWorkspace#run(ICoreRunnable, IProgressMonitor) + */ +public interface IWorkspaceRunnable extends ICoreRunnable { + /** + * @param monitor a progress monitor, or {@code null} if progress reporting and + * cancellation are not desired. The monitor is only valid for the duration + * of the invocation of this method. Callers may call {@link IProgressMonitor#done()} + * after this method returns or throws an exception, but this is not strictly + * required. + * @exception CoreException if this operation fails + * @exception OperationCanceledException if this operation is canceled + */ + @Override + public void run(IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java new file mode 100644 index 0000000000..ed5654de41 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java @@ -0,0 +1,472 @@ +/******************************************************************************* + * 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 + * Anton Leherbauer (Wind River) - [198591] Allow Builder to specify scheduling rule + * Anton Leherbauer (Wind River) - [305858] Allow Builder to return null rule + * James Blackburn (Broadcom) - [306822] Provide Context for Builder getRule() + * Broadcom Corporation - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.util.Map; +import org.eclipse.core.internal.events.InternalBuilder; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * The abstract base class for all incremental project builders. This class + * provides the infrastructure for defining a builder and fulfills the contract + * specified by the org.eclipse.core.resources.builders standard + * extension point. + *

                  + * All builders must subclass this class according to the following guidelines: + *

                    + *
                  • must re-implement at least build
                  • + *
                  • may implement other methods
                  • + *
                  • must supply a public, no-argument constructor
                  • + *
                  + * On creation, the setInitializationData method is called with + * any parameter data specified in the declaring plug-in's manifest. + */ +public abstract class IncrementalProjectBuilder extends InternalBuilder implements IExecutableExtension { + /** + * Build kind constant (value 6) indicating a full build request. A full + * build discards all previously built state and builds all resources again. + * Resource deltas are not applicable for this kind of build. + *

                  + * Note: If there is no previous delta, a request for {@link #INCREMENTAL_BUILD} + * or {@link #AUTO_BUILD} will result in the builder being called with {@link #FULL_BUILD} + * build kind. + *

                  + * + * @see IProject#build(int, IProgressMonitor) + * @see IProject#build(int, String, Map, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + */ + public static final int FULL_BUILD = 6; + /** + * Build kind constant (value 9) indicating an automatic build request. When + * autobuild is turned on, these builds are triggered automatically whenever + * resources change. Apart from the method by which autobuilds are triggered, + * they otherwise operate like an incremental build. + * + * @see IWorkspaceDescription#setAutoBuilding(boolean) + * @see IWorkspace#isAutoBuilding() + */ + public static final int AUTO_BUILD = 9; + /** + * Build kind constant (value 10) indicating an incremental build request. + * Incremental builds use an {@link IResourceDelta} that describes what + * resources have changed since the last build. The builder calculates + * what resources are affected by the delta, and rebuilds the affected resources. + * + * @see IProject#build(int, IProgressMonitor) + * @see IProject#build(int, String, Map, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + */ + public static final int INCREMENTAL_BUILD = 10; + /** + * Build kind constant (value 15) indicating a clean build request. A clean + * build discards any additional state that has been computed as a result of + * previous builds, and returns the project to a clean slate. Resource + * deltas are not applicable for this kind of build. + * + * @see IProject#build(int, IProgressMonitor) + * @see IProject#build(int, String, Map, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + * @see #clean(IProgressMonitor) + * @since 3.0 + */ + public static final int CLEAN_BUILD = 15; + + /** + * Runs this builder in the specified manner. Subclasses should implement + * this method to do the processing they require. + *

                  + * If the build kind is {@link #INCREMENTAL_BUILD} or + * {@link #AUTO_BUILD}, the getDelta method can be + * used during the invocation of this method to obtain information about + * what changes have occurred since the last invocation of this method. Any + * resource delta acquired is valid only for the duration of the invocation + * of this method. A {@link #FULL_BUILD} has no associated build delta. + *

                  + *

                  + * After completing a build, this builder may return a list of projects for + * which it requires a resource delta the next time it is run. This + * builder's project is implicitly included and need not be specified. The + * build mechanism will attempt to maintain and compute deltas relative to + * the identified projects when asked the next time this builder is run. + * Builders must re-specify the list of interesting projects every time they + * are run as this is not carried forward beyond the next build. Projects + * mentioned in return value but which do not exist will be ignored and no + * delta will be made available for them. + *

                  + *

                  + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. All builders should report their progress and + * honor cancel requests in a timely manner. Cancelation requests should be + * propagated to the caller by throwing + * OperationCanceledException. + *

                  + *

                  + * All builders should try to be robust in the face of trouble. In + * situations where failing the build by throwing CoreException + * is the only option, a builder has a choice of how best to communicate the + * problem back to the caller. One option is to use the + * {@link IResourceStatus#BUILD_FAILED} status code along with a suitable message; + * another is to use a {@link MultiStatus} containing finer-grained problem + * diagnoses. + *

                  + * + * @param kind the kind of build being requested. Valid values are + *
                    + *
                  • {@link #FULL_BUILD} - indicates a full build.
                  • + *
                  • {@link #INCREMENTAL_BUILD}- indicates an incremental build.
                  • + *
                  • {@link #AUTO_BUILD} - indicates an automatically triggered + * incremental build (autobuilding on).
                  • + *
                  + * @param args a table of builder-specific arguments keyed by argument name + * (key type: String, value type: String); + * null is equivalent to an empty map + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @return the list of projects for which this builder would like deltas the + * next time it is run or null if none + * @exception CoreException if this build fails. + * @see IProject#build(int, String, Map, IProgressMonitor) + */ + @Override + protected abstract IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException; + + /** + * Clean is an opportunity for a builder to discard any additional state that has + * been computed as a result of previous builds. It is recommended that builders + * override this method to delete all derived resources created by previous builds, + * and to remove all markers of type {@link IMarker#PROBLEM} that + * were created by previous invocations of the builder. The platform will + * take care of discarding the builder's last built state (there is no need + * to call forgetLastBuiltState). + *

                  + *

                  + * This method is called as a result of invocations of + * IWorkspace.build or IProject.build where + * the build kind is {@link #CLEAN_BUILD}. + *

                  + * This default implementation does nothing. Subclasses may override. + *

                  + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. All builders should report their progress and + * honor cancel requests in a timely manner. Cancelation requests should be + * propagated to the caller by throwing + * OperationCanceledException. + *

                  + * + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @exception CoreException if this build fails. + * @see IWorkspace#build(int, IProgressMonitor) + * @see #CLEAN_BUILD + * @since 3.0 + */ + @Override + protected void clean(IProgressMonitor monitor) throws CoreException { + //default implementation does nothing + //thwart compiler warning + } + + /** + * Requests that this builder forget any state it may be retaining regarding + * previously built states. Typically this means that the next time the + * builder runs, it will have to do a full build since it does not have any + * state upon which to base an incremental build. + * This supersedes a call to {@link #rememberLastBuiltState()}. + */ + @Override + public final void forgetLastBuiltState() { + super.forgetLastBuiltState(); + } + + /** + * Requests that this builder remember any build invocation specific state. + * This means that the next time the builder runs, it will receive a delta + * which includes changes reported in the current {@link #getDelta(IProject)}. + *

                  + * This can be used to indicate that a builder didn't run, even though there + * are changes, and the builder wishes that the delta be preserved until its + * next invocation. + *

                  + * This is superseded by a call to {@link #forgetLastBuiltState()}. + * @since 3.7 + */ + @Override + public final void rememberLastBuiltState() { + super.rememberLastBuiltState(); + } + + /** + * Returns the build command associated with this builder. The returned + * command may or may not be in the build specification for the project + * on which this builder operates. + *

                  + * Any changes made to the returned command will only take effect if + * the modified command is installed on a project build spec. + *

                  + * + * @see IProjectDescription#setBuildSpec(ICommand []) + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @since 3.1 + */ + @Override + public final ICommand getCommand() { + return super.getCommand(); + } + + /** + * Returns the resource delta recording the changes in the given project + * since the last time this builder was run. null is returned + * if no such delta is available. An empty delta is returned if no changes + * have occurred, or if deltas are not applicable for the current build kind. + * If null is returned, clients should assume + * that unspecified changes have occurred and take the appropriate action. + *

                  + * The system reserves the right to trim old state in an effort to conserve + * space. As such, callers should be prepared to receive null + * even if they previously requested a delta for a particular project by + * returning that project from a build call. + *

                  + *

                  + * A non- null delta will only be supplied for the given + * project if either the result returned from the previous + * build included the project or the project is the one + * associated with this builder. + *

                  + *

                  + * If the given project was mentioned in the previous build + * and subsequently deleted, a non- null delta containing the + * deletion will be returned. If the given project was mentioned in the + * previous build and was subsequently created, the returned + * value will be null. + *

                  + *

                  + * A valid delta will be returned only when this method is called during a + * build. The delta returned will be valid only for the duration of the + * enclosing build execution. + *

                  + *

                  + * The delta does not include changes made while this builder is running. + * If {@link #getRule(int, Map)} is overridden to return a scheduling rule other than + * the workspace root, changes performed in other threads during the build + * will not appear in the resource delta. + *

                  + * + * @return the resource delta for the project or null + */ + @Override + public final IResourceDelta getDelta(IProject project) { + return super.getDelta(project); + } + + /** + * Returns the project for which this builder is defined. + * + * @return the project + */ + @Override + public final IProject getProject() { + return super.getProject(); + } + + /** + * Returns the build configuration for which this build was invoked. + * @return the build configuration + * @since 3.7 + */ + @Override + public final IBuildConfiguration getBuildConfig() { + return super.getBuildConfig(); + } + + /** + * Returns whether the given project has already been built during this + * build iteration. + *

                  + * When the entire workspace is being built, the projects are built in + * linear sequence. This method can be used to determine if another project + * precedes this builder's project in that build sequence. If only a single + * project is being built, then there is no build order and this method will + * always return false. + *

                  + * + * @param project the project to check against in the current build order + * @return true if the given project has been built in this + * iteration, and false otherwise. + * @see #needRebuild() + * @since 2.1 + */ + @Override + public final boolean hasBeenBuilt(IProject project) { + return super.hasBeenBuilt(project); + } + + /** + * Returns whether an interrupt request has been made for this build. + * Background autobuild is interrupted when another thread tries to modify + * the workspace concurrently with the build thread. When this occurs, the + * build cycle is flagged as interrupted and the build will be terminated at + * the earliest opportunity. This method allows long running builders to + * respond to this interruption in a timely manner. Builders are not + * required to respond to interruption requests. + *

                  + * + * @return true if the build cycle has been interrupted, and + * false otherwise. + * @since 3.0 + */ + @Override + public final boolean isInterrupted() { + return super.isInterrupted(); + } + + /** + * Indicates that this builder made changes that affect a build configuration that + * precedes this build configuration in the currently executing build order, and thus a + * rebuild will be necessary. + *

                  + * This is an advanced feature that builders should use with caution. This + * can cause workspace builds to iterate until no more builders require + * rebuilds. + *

                  + * + * @see #hasBeenBuilt(IProject) + * @since 2.1 + */ + @Override + public final void needRebuild() { + super.needRebuild(); + } + + /** + * Sets initialization data for this builder. + *

                  + * This method is part of the {@link IExecutableExtension} interface. + *

                  + *

                  + * Subclasses are free to extend this method to pick up initialization + * parameters from the plug-in plug-in manifest (plugin.xml) + * file, but should be sure to invoke this method on their superclass. + *

                  + * For example, the following method looks for a boolean-valued parameter + * named "trace": + * + *

                  +	 * public void setInitializationData(IConfigurationElement cfig, String propertyName, Object data) throws CoreException {
                  +	 * 	super.setInitializationData(cfig, propertyName, data);
                  +	 * 	if (data instanceof Hashtable) {
                  +	 * 		Hashtable args = (Hashtable) data;
                  +	 * 		String traceValue = (String) args.get("trace");
                  +	 * 		TRACING = (traceValue != null && traceValue.equals("true"));
                  +	 * 	}
                  +	 * }
                  +	 * 
                  + *

                  + * @throws CoreException if fails. + */ + @Override + public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException { + //default implementation does nothing + //thwart compiler warning + } + + /** + * Informs this builder that it is being started by the build management + * infrastructure. By the time this method is run, the builder's project is + * available and setInitializationData has been called. The + * default implementation should be called by all overriding methods. + * + * @see #setInitializationData(IConfigurationElement, String, Object) + */ + @Override + protected void startupOnInitialize() { + // reserved for future use + } + + /** + * Returns the scheduling rule that is required for building + * the project build configuration for which this builder is defined. The default + * is the workspace root rule. + *

                  + * The scheduling rule determines which resources in the workspace are + * protected from being modified by other threads while the builder is running. Up until + * Eclipse 3.5, the entire workspace was always locked during a build; + * since Eclipse 3.6, builders can allow resources outside their scheduling + * rule to be modified. + *

                  + * Notes: + *

                    + *
                  • + * If the builder rule is non-null it must be "contained" in the workspace root rule. + * I.e. {@link ISchedulingRule#contains(ISchedulingRule)} must return + * true when invoked on the workspace root with the builder rule. + *
                  • + *
                  • + * The rule returned here may have no effect if the build is invoked within the + * scope of another operation that locks the entire workspace. + *
                  • + *
                  • + * If this method returns any rule other than the workspace root, + * resources outside of the rule scope can be modified concurrently with the build. + * The delta returned by {@link #getDelta(IProject)} for any project + * outside the scope of the builder's rule may not contain changes that occurred + * concurrently with the build. + *
                  + *

                  + *

                  + * Subclasses may override this method. + *

                  + * @noreference This method is not intended to be referenced by clients. + * + * @param kind the kind of build being requested. Valid values include: + *
                    + *
                  • {@link #FULL_BUILD} - indicates a full build.
                  • + *
                  • {@link #INCREMENTAL_BUILD} - indicates an incremental build.
                  • + *
                  • {@link #AUTO_BUILD} - indicates an automatically triggered + * incremental build (autobuilding on).
                  • + *
                  • {@link #CLEAN_BUILD} - indicates a clean request.
                  • + *
                  + * @param args a table of builder-specific arguments keyed by argument name + * (key type: String, value type: String); + * null is equivalent to an empty map. + * @return a scheduling rule which is contained in the workspace root rule + * or null to indicate that no protection against resource + * modification during the build is needed. + * + * @since 3.6 + */ + public ISchedulingRule getRule(int kind, Map args) { + return ResourcesPlugin.getWorkspace().getRoot(); + } + + /** + * Get the context for this invocation of the builder. This is only valid + * in the context of a call to + * {@link #build(int, Map, IProgressMonitor)} + * + *

                  + * This can be used to discover which build configurations are being built before + * and after this build configuration. + *

                  + * + * @return the context for the most recent invocation of the builder + * @since 3.7 + */ + @Override + public final IBuildContext getContext() { + return super.getContext(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java new file mode 100644 index 0000000000..411b003303 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * 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 Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.internal.preferences.AbstractScope; +import org.eclipse.core.internal.preferences.EclipsePreferences; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; + +/** + * Object representing the project scope in the Eclipse preferences + * hierarchy. Can be used as a context for searching for preference + * values (in the org.eclipse.core.runtime.IPreferencesService + * APIs) or for determining the correct preference node to set values in the store. + *

                  + * Project preferences are stored on a per project basis in the + * project's content area as specified by IProject#getLocation. + *

                  + * The path for preferences defined in the project scope hierarchy + * is as follows: /project/<projectName>/<qualifier> + *

                  + *

                  + * This class is not intended to be subclassed. This class may be instantiated. + *

                  + * @see IProject#getLocation() + * @since 3.0 + */ +public final class ProjectScope extends AbstractScope { + + /** + * String constant (value of "project") used for the + * scope name for this preference scope. + */ + public static final String SCOPE = "project"; //$NON-NLS-1$ + + private IProject context; + + /** + * Create and return a new project scope for the given project. The given + * project must not be null. + * + * @param context the project + * @exception IllegalArgumentException if the project is null + */ + public ProjectScope(IProject context) { + super(); + if (context == null) + throw new IllegalArgumentException(); + this.context = context; + } + + /* + * @see org.eclipse.core.runtime.IScopeContext#getNode(java.lang.String) + */ + @Override + public IEclipsePreferences getNode(String qualifier) { + if (qualifier == null) + throw new IllegalArgumentException(); + return (IEclipsePreferences) Platform.getPreferencesService().getRootNode().node(SCOPE).node(context.getName()).node(qualifier); + } + + /* + * @see org.eclipse.core.runtime.preferences.IScopeContext#getLocation() + */ + @Override + public IPath getLocation() { + IProject project = ((IResource) context).getProject(); + IPath location = project.getLocation(); + return location == null ? null : location.append(EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME); + } + + /* + * @see org.eclipse.core.runtime.preferences.IScopeContext#getName() + */ + @Override + public String getName() { + return SCOPE; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (!(obj instanceof ProjectScope)) + return false; + ProjectScope other = (ProjectScope) obj; + return context.equals(other.context); + } + + @Override + public int hashCode() { + return super.hashCode() + context.getFullPath().hashCode(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java new file mode 100644 index 0000000000..b0cea77f93 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java @@ -0,0 +1,234 @@ +/******************************************************************************* + * Copyright (c) 2004, 2014 Red Hat Incorporated 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/org/documents/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API + * Red Hat Incorporated - initial implementation + * Martin Oberhuber (Wind River) - [44107] Add symbolic links to ResourceAttributes API + *******************************************************************************/ + +package org.eclipse.core.resources; + +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileSystem; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.runtime.CoreException; + +/** + * This class represents platform specific attributes of files. + * Any attributes can be added, but only the attributes that are + * supported by the platform will be used. These methods do not set the + * attributes in the file system. + * + * @author Red Hat Incorporated + * @see IResource#getResourceAttributes() + * @see IResource#setResourceAttributes(ResourceAttributes) + * @since 3.1 + * @noextend This class is not intended to be subclassed by clients. + */ +public class ResourceAttributes { + private int attributes; + + /** + * Creates a new resource attributes instance with attributes + * taken from the specified file in the file system. If the specified + * file does not exist or is not accessible, this method has the + * same effect as calling the default constructor. + * + * @param file The file to get attributes from + * @return A resource attributes object + */ + public static ResourceAttributes fromFile(java.io.File file) { + try { + return FileUtil.fileInfoToAttributes(EFS.getStore(file.toURI()).fetchInfo()); + } catch (CoreException e) { + //file could not be accessed + return new ResourceAttributes(); + } + } + + /** + * Creates a new instance of ResourceAttributes. + */ + public ResourceAttributes() { + super(); + } + + /** + * Returns whether this ResourceAttributes object is marked archive. + *

                  This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_ARCHIVE}.

                  + * + * @return true if this resource is marked archive, + * false otherwise + * @see #setArchive(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_ARCHIVE + */ + public boolean isArchive() { + return (attributes & EFS.ATTRIBUTE_ARCHIVE) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked executable. + *

                  This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_EXECUTABLE}.

                  + * + * @return true if this resource is marked executable, + * false otherwise + * @see #setExecutable(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_EXECUTABLE + */ + public boolean isExecutable() { + return (attributes & EFS.ATTRIBUTE_EXECUTABLE) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked hidden. + *

                  This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_HIDDEN}.

                  + * + * @return true if this resource is marked hidden, + * false otherwise + * @see #setHidden(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_HIDDEN + * @since 3.2 + */ + public boolean isHidden() { + return (attributes & EFS.ATTRIBUTE_HIDDEN) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked read only. + *

                  This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_READ_ONLY}.

                  + * + * @return true if this resource is marked as read only, + * false otherwise + * @see #setReadOnly(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_READ_ONLY + */ + public boolean isReadOnly() { + return (attributes & EFS.ATTRIBUTE_READ_ONLY) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked as symbolic link. + *

                  This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_SYMLINK}.

                  + * + * @return true if this resource is marked as symbolic link, + * false otherwise + * @see #setSymbolicLink(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_SYMLINK + * @since 3.4 + */ + public boolean isSymbolicLink() { + return (attributes & EFS.ATTRIBUTE_SYMLINK) != 0; + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked archive. + *

                  This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_ARCHIVE}.

                  + * + * @param archive true to set it to be archive, + * false to unset + * @see #isArchive() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_ARCHIVE + */ + public void setArchive(boolean archive) { + set(EFS.ATTRIBUTE_ARCHIVE, archive); + } + + /** + * Clears all of the bits indicated by the mask. + * @nooverride This method is not intended to be re-implemented or extended by clients. + * @noreference This method is not intended to be referenced by clients. + */ + public void set(int mask, boolean value) { + if (value) + attributes |= mask; + else + attributes &= ~mask; + } + + /** + * Returns whether this ResourceAttributes object has the given mask set. + * @nooverride This method is not intended to be re-implemented or extended by clients. + * @noreference This method is not intended to be referenced by clients. + */ + public boolean isSet(int mask) { + return (attributes & mask) != 0; + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked executable. + *

                  This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_EXECUTABLE}.

                  + * + * @param executable true to set it to be executable, + * false to unset + * @see #isExecutable() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_EXECUTABLE + */ + public void setExecutable(boolean executable) { + set(EFS.ATTRIBUTE_EXECUTABLE, executable); + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked hidden + *

                  This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_HIDDEN}.

                  + * + * @param hidden true to set it to be marked hidden, + * false to unset + * @see #isHidden() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_HIDDEN + * @since 3.2 + */ + public void setHidden(boolean hidden) { + set(EFS.ATTRIBUTE_HIDDEN, hidden); + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked read only. + *

                  This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_READ_ONLY}.

                  + * + * @param readOnly true to set it to be marked read only, + * false to unset + * @see #isReadOnly() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_READ_ONLY + */ + public void setReadOnly(boolean readOnly) { + set(EFS.ATTRIBUTE_READ_ONLY, readOnly); + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked as symbolic link. + *

                  This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_SYMLINK}.

                  + * + * @param symLink true to set it to be marked as symbolic link, + * false to unset + * @see #isSymbolicLink() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_SYMLINK + * @since 3.4 + */ + public void setSymbolicLink(boolean symLink) { + set(EFS.ATTRIBUTE_SYMLINK, symLink); + } + + /** + * Returns a string representation of the attributes, suitable + * for debugging purposes only. + */ + @Override + public String toString() { + return "ResourceAttributes(" + attributes + ')'; //$NON-NLS-1$ + } +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java new file mode 100644 index 0000000000..fbc9acae39 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java @@ -0,0 +1,485 @@ +/******************************************************************************* + * 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 PT_FILTER_PROVIDERS + * Serge Beauchamp (Freescale Semiconductor) - [229633] add PT_VARIABLE_PROVIDERS + * James Blackburn (Broadcom Corp.) - ongoing development + * Tom Hochstein (Freescale) - Bug 409996 - 'Restore Defaults' does not work properly on Project Properties > Resource tab + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.util.*; +import org.eclipse.core.internal.preferences.PreferencesService; +import org.eclipse.core.internal.resources.*; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.IJobManager; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.service.debug.DebugOptions; +import org.eclipse.osgi.service.debug.DebugOptionsListener; +import org.osgi.framework.*; + +/** + * The plug-in runtime class for the Resources plug-in. This is + * the starting point for all workspace and resource manipulation. + * A typical sequence of events would be for a dependent plug-in + * to call ResourcesPlugin.getWorkspace(). + * Doing so would cause this plug-in to be activated and the workspace + * (if any) to be loaded from disk and initialized. + * + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class ResourcesPlugin extends Plugin { + /** + * Unique identifier constant (value "org.eclipse.core.resources") + * for the standard Resources plug-in. + */ + public static final String PI_RESOURCES = "org.eclipse.core.resources"; //$NON-NLS-1$ + + /*==================================================================== + * Constants defining the ids of the standard workspace extension points: + *====================================================================*/ + + /** + * Simple identifier constant (value "builders") + * for the builders extension point. + */ + public static final String PT_BUILDERS = "builders"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "natures") + * for the natures extension point. + */ + public static final String PT_NATURES = "natures"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "markers") + * for the markers extension point. + */ + public static final String PT_MARKERS = "markers"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "fileModificationValidator") + * for the file modification validator extension point. + */ + public static final String PT_FILE_MODIFICATION_VALIDATOR = "fileModificationValidator"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "moveDeleteHook") + * for the move/delete hook extension point. + * + * @since 2.0 + */ + public static final String PT_MOVE_DELETE_HOOK = "moveDeleteHook"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "teamHook") + * for the team hook extension point. + * + * @since 2.1 + */ + public static final String PT_TEAM_HOOK = "teamHook"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "refreshProviders") + * for the auto-refresh refresh providers extension point. + * + * @since 3.0 + */ + public static final String PT_REFRESH_PROVIDERS = "refreshProviders"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "modelProviders") + * for the model providers extension point. + * + * @since 3.2 + */ + public static final String PT_MODEL_PROVIDERS = "modelProviders"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "variableProviders") + * for the variable providers extension point. + * + * @since 3.6 + */ + public static final String PT_VARIABLE_PROVIDERS = "variableResolvers"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "filterMatchers") + * for the filter matchers extension point. + * + * @since 3.6 + */ + public static final String PT_FILTER_MATCHERS = "filterMatchers"; //$NON-NLS-1$ + + /** + * Constant identifying the job family identifier for the background autobuild job. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @since 3.0 + */ + public static final Object FAMILY_AUTO_BUILD = new Object(); + + /** + * Constant identifying the job family identifier for the background auto-refresh job. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @since 3.1 + */ + public static final Object FAMILY_AUTO_REFRESH = new Object(); + + /** + * Constant identifying the job family identifier for a background build job. All clients + * that schedule background jobs for performing builds should include this job + * family in their implementation of belongsTo. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @see Job#belongsTo(Object) + * @since 3.0 + */ + public static final Object FAMILY_MANUAL_BUILD = new Object(); + + /** + * Constant identifying the job family identifier for a background refresh job. All clients + * that schedule background jobs for performing refreshing should include this job + * family in their implementation of belongsTo. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @see Job#belongsTo(Object) + * @since 3.4 + */ + public static final Object FAMILY_MANUAL_REFRESH = new Object(); + + /** + * Name of a preference indicating the encoding to use when reading text + * files in the workspace. The value is a string, and may + * be the default empty string, indicating that the file system encoding should + * be used instead. The file system encoding can be retrieved using + * System.getProperty("file.encoding"). + * There is also a convenience method getEncoding which returns + * the value of this preference, or the file system encoding if this + * preference is not set. + *

                  + * Note that there is no guarantee that the value is a supported encoding. + * Callers should be prepared to handle UnsupportedEncodingException + * where this encoding is used. + *

                  + * + * @see #getEncoding() + * @see java.io.UnsupportedEncodingException + */ + public static final String PREF_ENCODING = "encoding"; //$NON-NLS-1$ + + /** + * Common prefix for workspace preference names. + * @since 2.1 + */ + private static final String PREF_DESCRIPTION_PREFIX = "description."; //$NON-NLS-1$ + + /** + * @deprecated Do not use. + * @since 3.0 + */ + @Deprecated + public static final String PREF_MAX_NOTIFICATION_DELAY = "maxnotifydelay"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether the workspace performs auto- + * builds. + * + * @see IWorkspaceDescription#isAutoBuilding() + * @see IWorkspaceDescription#setAutoBuilding(boolean) + * @since 2.1 + */ + public static final String PREF_AUTO_BUILDING = PREF_DESCRIPTION_PREFIX + "autobuilding"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the order projects in the workspace + * are built. + * + * @see IWorkspaceDescription#getBuildOrder() + * @see IWorkspaceDescription#setBuildOrder(String[]) + * @since 2.1 + */ + public static final String PREF_BUILD_ORDER = PREF_DESCRIPTION_PREFIX + "buildorder"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether to use the workspace's + * default order for building projects. + * @since 2.1 + */ + public static final String PREF_DEFAULT_BUILD_ORDER = PREF_DESCRIPTION_PREFIX + "defaultbuildorder"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum number of times that the + * workspace should rebuild when builders affect projects that have already + * been built. + * + * @see IWorkspaceDescription#getMaxBuildIterations() + * @see IWorkspaceDescription#setMaxBuildIterations(int) + * @since 2.1 + */ + public static final String PREF_MAX_BUILD_ITERATIONS = PREF_DESCRIPTION_PREFIX + "maxbuilditerations"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether to apply the specified history size policy. + * + * @see IWorkspaceDescription#isApplyFileStatePolicy() + * @see IWorkspaceDescription#setApplyFileStatePolicy(boolean) + * @since 3.6 + */ + public static final String PREF_APPLY_FILE_STATE_POLICY = PREF_DESCRIPTION_PREFIX + "applyfilestatepolicy"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum number of milliseconds a + * file state should be kept in the local history + * + * @see IWorkspaceDescription#getFileStateLongevity() + * @see IWorkspaceDescription#setFileStateLongevity(long) + * @since 2.1 + */ + public static final String PREF_FILE_STATE_LONGEVITY = PREF_DESCRIPTION_PREFIX + "filestatelongevity"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum permitted size of a file + * to be stored in the local history + * + * @see IWorkspaceDescription#getMaxFileStateSize() + * @see IWorkspaceDescription#setMaxFileStateSize(long) + * @since 2.1 + */ + public static final String PREF_MAX_FILE_STATE_SIZE = PREF_DESCRIPTION_PREFIX + "maxfilestatesize"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum number of states per + * file that can be stored in the local history. + * + * @see IWorkspaceDescription#getMaxFileStates() + * @see IWorkspaceDescription#setMaxFileStates(int) + * @since 2.1 + */ + public static final String PREF_MAX_FILE_STATES = PREF_DESCRIPTION_PREFIX + "maxfilestates"; //$NON-NLS-1$ + /** + * Name of a preference for configuring the amount of time in milliseconds + * between automatic workspace snapshots + * + * @see IWorkspaceDescription#getSnapshotInterval() + * @see IWorkspaceDescription#setSnapshotInterval(long) + * @since 2.1 + */ + public static final String PREF_SNAPSHOT_INTERVAL = PREF_DESCRIPTION_PREFIX + "snapshotinterval"; //$NON-NLS-1$ + + /** + * Name of a preference for turning off support for linked resources. When + * this preference is set to "true", attempting to create linked resources will fail. + * @since 2.1 + */ + public static final String PREF_DISABLE_LINKING = PREF_DESCRIPTION_PREFIX + "disableLinking";//$NON-NLS-1$ + + /** + * Name of a preference for configuring whether the workspace performs auto- + * refresh. Auto-refresh installs a file-system listener, or performs + * periodic file-system polling to actively discover changes in the resource + * hierarchy. + * @since 3.0 + */ + public static final String PREF_AUTO_REFRESH = "refresh.enabled"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether out-of-sync resources are automatically + * asynchronously refreshed, when discovered to be out-of-sync by the workspace. + *

                  + * This preference suppresses out-of-sync CoreException for some read methods, including: + * {@link IFile#getContents()} & {@link IFile#getContentDescription()}. + *

                  + *

                  + * In the future the workspace may enable other lightweight auto-refresh mechanisms when this + * preference is true. (The existing {@link ResourcesPlugin#PREF_AUTO_REFRESH} will continue + * to enable filesystem hooks and the existing polling based monitor.) + *

                  + * See the discussion: https://bugs.eclipse.org/303517 + * @since 3.7 + */ + public static final String PREF_LIGHTWEIGHT_AUTO_REFRESH = "refresh.lightweight.enabled"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether encodings for derived + * resources within the project should be stored in a separate derived + * preference file. + * + * @since 3.7 + */ + public static final String PREF_SEPARATE_DERIVED_ENCODINGS = "separateDerivedEncodings"; //$NON-NLS-1$ + + /** + * Default setting for {@value #PREF_SEPARATE_DERIVED_ENCODINGS}. + * + * @since 3.9 + */ + public static final boolean DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS = false; + + /** + * The single instance of this plug-in runtime class. + */ + private static ResourcesPlugin plugin; + + /** + * The workspace managed by the single instance of this + * plug-in runtime class, or null is there is none. + */ + private static Workspace workspace = null; + + private ServiceRegistration workspaceRegistration; + private ServiceRegistration debugRegistration; + + /** + * Constructs an instance of this plug-in runtime class. + *

                  + * An instance of this plug-in runtime class is automatically created + * when the facilities provided by the Resources plug-in are required. + * Clients must never explicitly instantiate a plug-in runtime class. + *

                  + */ + public ResourcesPlugin() { + plugin = this; + } + + /** + * Constructs a brand new workspace structure at the location in the local file system + * identified by the given path and returns a new workspace object. + * + * @exception CoreException if the workspace structure could not be constructed. + * Reasons include: + *
                    + *
                  • There is an existing workspace structure on at the given location + * in the local file system. + *
                  • A file exists at the given location in the local file system. + *
                  • A directory could not be created at the given location in the + * local file system. + *
                  + */ + private static void constructWorkspace() throws CoreException { + new LocalMetaArea().createMetaArea(); + } + + /** + * Returns the encoding to use when reading text files in the workspace. + * This is the value of the PREF_ENCODING preference, or the + * file system encoding (System.getProperty("file.encoding")) + * if the preference is not set. + *

                  + * Note that this method does not check whether the result is a supported + * encoding. Callers should be prepared to handle + * UnsupportedEncodingException where this encoding is used. + * + * @return the encoding to use when reading text files in the workspace + * @see java.io.UnsupportedEncodingException + */ + public static String getEncoding() { + String enc = getPlugin().getPluginPreferences().getString(PREF_ENCODING); + if (enc == null || enc.length() == 0) { + enc = System.getProperty("file.encoding"); //$NON-NLS-1$ + } + return enc; + } + + /** + * Returns the Resources plug-in. + * + * @return the single instance of this plug-in runtime class + */ + public static ResourcesPlugin getPlugin() { + return plugin; + } + + /** + * Returns the workspace. The workspace is not accessible after the resources + * plug-in has shutdown. + * + * @return the workspace that was created by the single instance of this + * plug-in class. + */ + public static IWorkspace getWorkspace() { + if (workspace == null) + throw new IllegalStateException(Messages.resources_workspaceClosed); + return workspace; + } + + /** + * This implementation of the corresponding {@link BundleActivator} method + * closes the workspace without saving. + * @see BundleActivator#stop(BundleContext) + */ + @Override + public void stop(BundleContext context) throws Exception { + super.stop(context); + + // unregister debug options listener + debugRegistration.unregister(); + debugRegistration = null; + + if (workspace == null) + return; + workspaceRegistration.unregister(); + // save the preferences for this plug-in + getPlugin().savePluginPreferences(); + workspace.close(null); + + // Forget workspace only if successfully closed, to + // make it easier to debug cases where close() is failing. + workspace = null; + workspaceRegistration = null; + } + + /** + * This implementation of the corresponding {@link BundleActivator} method + * opens the workspace. + * @see BundleActivator#start(BundleContext) + */ + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + System.err.println("RESOURCES: BUG 519776"); + + // register debug options listener + Hashtable properties = new Hashtable<>(2); + properties.put(DebugOptions.LISTENER_SYMBOLICNAME, PI_RESOURCES); + debugRegistration = context.registerService(DebugOptionsListener.class, Policy.RESOURCES_DEBUG_OPTIONS_LISTENER, properties); + + if (!new LocalMetaArea().hasSavedWorkspace()) { + constructWorkspace(); + } + // Remember workspace before opening, to + // make it easier to debug cases where open() is failing. + workspace = new Workspace(); + PlatformURLResourceConnection.startup(workspace.getRoot().getLocation()); + initializePreferenceLookupOrder(); + IStatus result = workspace.open(null); + if (!result.isOK()) + getLog().log(result); + workspaceRegistration = context.registerService(IWorkspace.class, workspace, null); + } + + /* + * Add the project scope to the preference service's default look-up order so + * people get it for free + */ + private void initializePreferenceLookupOrder() { + PreferencesService service = PreferencesService.getDefault(); + String[] original = service.getDefaultDefaultLookupOrder(); + List newOrder = new ArrayList<>(); + // put the project scope first on the list + newOrder.add(ProjectScope.SCOPE); + for (String entry : original) + newOrder.add(entry); + service.setDefaultDefaultLookupOrder(newOrder.toArray(new String[newOrder.size()])); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java new file mode 100644 index 0000000000..039c28c807 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.internal.resources.InternalWorkspaceJob; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * A job that makes an atomic modification to the workspace. Clients must + * implement the abstract method runInWorkspace instead + * of the usual Job.run method. + *

                  + * After running a method that modifies resources in the workspace, + * registered listeners receive after-the-fact notification of + * what just transpired, in the form of a resource change event. + * This method allows clients to call a number of + * methods that modify resources and only have resource + * change event notifications reported at the end of the entire + * batch. This mechanism is used to avoid unnecessary builds + * and notifications. + *

                  + *

                  + * Platform may decide to perform notifications during the operation. + * The reason for this is that it is possible for multiple threads + * to be modifying the workspace concurrently. When one thread finishes modifying + * the workspace, a notification is required to prevent responsiveness problems, + * even if the other operation has not yet completed. + *

                  + *

                  + * A WorkspaceJob is the asynchronous equivalent of ICoreRunnable + *

                  + *

                  + * Note that the workspace is not locked against other threads during the execution + * of a workspace job. Other threads can be modifying the workspace concurrently + * with a workspace job. To obtain exclusive access to a portion of the workspace, + * set the scheduling rule on the job to be a resource scheduling rule. The + * interface IResourceRuleFactory is used to create a scheduling rule + * for a particular workspace modification operation. + *

                  + * @see ICoreRunnable + * @see org.eclipse.core.resources.IResourceRuleFactory + * @see IWorkspace#run(ICoreRunnable, ISchedulingRule, int, IProgressMonitor) + * @since 3.0 + */ +public abstract class WorkspaceJob extends InternalWorkspaceJob { + /** + * Creates a new workspace 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 WorkspaceJob(String name) { + super(name); + } + + /** + * Runs the operation, reporting progress to and accepting + * cancellation requests from the given progress monitor. + *

                  + * Implementors of this method should check the progress monitor + * for cancellation when it is safe and appropriate to do so. The cancellation + * request should be propagated to the caller by throwing + * OperationCanceledException. + *

                  + * + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @return the result of running the operation + * @exception CoreException if this operation fails. + */ + @Override + public abstract IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java new file mode 100644 index 0000000000..cafa50fa25 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 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 + *******************************************************************************/ +package org.eclipse.core.resources.filtermatchers; + +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; + +/** + * The abstract base class for all file info matchers. Instances + * of this class are provided using the org.eclipse.core.resources.filterMatchers + * extension point. + * + * @since 3.6 + */ +public abstract class AbstractFileInfoMatcher { + + /** + * Tests the given {@link FileInfo} + * + * @param parent the parent container + * @param fileInfo the {@link FileInfo} object to test + * @return true if the given {@link FileInfo} matches, + * and false otherwise. + * @throws CoreException the implementor should throw a CoreException if, + * in the case that the parent or fileInfo doesn't exist in the workspace + * or in the file system, the return value can't be determined. + */ + public abstract boolean matches(IContainer parent, IFileInfo fileInfo) throws CoreException; + + /** + * Sets initialization data for this matcher. + * @param project + * @param arguments + * @throws CoreException if initialization failed + */ + public abstract void initialize(IProject project, Object arguments) throws CoreException; +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java new file mode 100644 index 0000000000..bf37be3b26 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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) - [252996] initial API and implementation + * IBM Corporation - ongoing implementation + *******************************************************************************/ +package org.eclipse.core.resources.filtermatchers; + +import org.eclipse.core.internal.resources.FilterDescriptor; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; + +/** + * Resource Filter Type allowing serializing sub filters as the arguments + * @since 3.6 + */ +public abstract class CompoundFileInfoMatcher extends AbstractFileInfoMatcher { + + protected AbstractFileInfoMatcher[] matchers; + + private AbstractFileInfoMatcher instantiate(IProject project, FileInfoMatcherDescription filter) throws CoreException { + IFilterMatcherDescriptor desc = project.getWorkspace().getFilterMatcherDescriptor(filter.getId()); + if (desc != null) { + AbstractFileInfoMatcher matcher = ((FilterDescriptor) desc).createFilter(); + matcher.initialize(project, filter.getArguments()); + return matcher; + } + return null; + } + + @Override + public final void initialize(IProject project, Object arguments) throws CoreException { + FileInfoMatcherDescription[] filters = (FileInfoMatcherDescription[]) arguments; + matchers = new AbstractFileInfoMatcher[filters != null ? filters.length : 0]; + for (int i = 0; i < matchers.length; i++) + matchers[i] = instantiate(project, filters[i]); + } +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html new file mode 100644 index 0000000000..7f43f8e202 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html @@ -0,0 +1,21 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the resource filter matchers. + +

                  Package Specification

                  +

                  +This package specifies the APIs in the Resources plug-in that are intended +to be implemented and used by plug-ins using the filterMatchers extension point. +

                  +@since 3.6 +

                  + + + diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java new file mode 100644 index 0000000000..98c3dd165c --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.*; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.*; + +/** + * A resource mapping that obtains the traversals for its model object + * from a set of child mappings. + *

                  + * This class is not intended to be subclasses by clients. + * + * @since 3.2 + */ +public final class CompositeResourceMapping extends ResourceMapping { + private final ResourceMapping[] mappings; + private final Object modelObject; + private IProject[] projects; + private String providerId; + + /** + * Create a composite mapping that obtains its traversals from a set of sub-mappings. + * @param modelObject the model object for this mapping + * @param mappings the sub-mappings from which the traversals are obtained + */ + public CompositeResourceMapping(String providerId, Object modelObject, ResourceMapping[] mappings) { + this.modelObject = modelObject; + this.mappings = mappings; + this.providerId = providerId; + } + + @Override + public boolean contains(ResourceMapping mapping) { + for (int i = 0; i < mappings.length; i++) { + ResourceMapping childMapping = mappings[i]; + if (childMapping.contains(mapping)) { + return true; + } + } + return false; + } + + /** + * Return the resource mappings contained in this composite. + * @return Return the resource mappings contained in this composite. + */ + public ResourceMapping[] getMappings() { + return mappings; + } + + @Override + public Object getModelObject() { + return modelObject; + } + + @Override + public String getModelProviderId() { + return providerId; + } + + @Override + public IProject[] getProjects() { + if (projects == null) { + Set result = new HashSet<>(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + result.addAll(Arrays.asList(mapping.getProjects())); + } + projects = result.toArray(new IProject[result.size()]); + } + return projects; + } + + @Override + public ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + SubMonitor subMonitor = SubMonitor.convert(monitor, mappings.length); + List result = new ArrayList<>(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + Collections.addAll(result, mapping.getTraversals(context, subMonitor.newChild(1))); + } + return result.toArray(new ResourceTraversal[result.size()]); + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java new file mode 100644 index 0000000000..669603bbee --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 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.mapping; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A model provider descriptor contains information about a model provider + * obtained from the plug-in manifest (plugin.xml) file. + *

                  + * Model provider descriptors are platform-defined objects that exist + * independent of whether that model provider's plug-in has been started. + * In contrast, a model provider's runtime object (ModelProvider) + * generally runs plug-in-defined code. + *

                  + * + * @see org.eclipse.core.resources.mapping.ModelProvider + * @since 3.2 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IModelProviderDescriptor { + + /** + * Return the ids of model providers that this model provider extends. + * @return the ids of model providers that this model provider extends + */ + public String[] getExtendedModels(); + + /** + * Returns the unique identifier of this model provider. + *

                  + * The model provider identifier is composed of the model provider's + * plug-in id and the simple id of the provider extension. For example, if + * plug-in "com.xyz" defines a provider extension with id + * "myModelProvider", the unique model provider identifier will be + * "com.xyz.myModelProvider". + *

                  + * + * @return the unique model provider identifier + */ + public String getId(); + + /** + * Returns a displayable label for this model provider. + * Returns the empty string if no label for this provider + * is specified in the plug-in manifest file. + *

                  Note that any translation specified in the plug-in manifest + * file is automatically applied. + *

                  + * + * @return a displayable string label for this model provider, + * possibly the empty string + */ + public String getLabel(); + + /** + * From the provides set of resources, return those that match the enablement + * rule specified for the model provider descriptor. The resource mappings + * for the returned resources can then be obtained by invoking + * {@link ModelProvider#getMappings(IResource[], ResourceMappingContext, IProgressMonitor)} + * + * @param resources the resources + * @return the resources that match the descriptor's enablement rule + */ + public IResource[] getMatchingResources(IResource[] resources) throws CoreException; + + /** + * Return the set of traversals that overlap with the resources that + * this descriptor matches. + * + * @param traversals the traversals being tested + * @return the subset of these traversals that overlap with the resources + * that match this descriptor + * @throws CoreException + */ + public ResourceTraversal[] getMatchingTraversals(ResourceTraversal[] traversals) throws CoreException; + + /** + * Return the model provider for this descriptor, instantiating it if it is + * the first time the method is called. + * + * @return the model provider for this descriptor + * @exception CoreException if the model provider could not be instantiated for + * some reason + */ + public ModelProvider getModelProvider() throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java new file mode 100644 index 0000000000..f28fdca41b --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 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.mapping; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * This factory is used to build a resource delta that represents a proposed change + * that can then be passed to the {@link ResourceChangeValidator#validateChange(IResourceDelta, IProgressMonitor)} + * method in order to validate the change with any model providers stored in those resources. + * The deltas created by calls to the methods of this interface will be the same as + * those generated by the workspace if the proposed operations were performed. + *

                  + * This factory does not validate that the proposed operation is valid given the current + * state of the resources and any other proposed changes. It only records the + * delta that would result. + * + * @see ResourceChangeValidator + * @see ModelProvider + * @since 3.2 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceChangeDescriptionFactory { + + /** + * Record a delta that represents a content change for the given file. + * @param file the file whose contents will be changed + */ + public void change(IFile file); + + /** + * Record the set of deltas representing the closed of a project. + * @param project the project that will be closed + */ + public void close(IProject project); + + /** + * Record the set of deltas representing a copy of the given resource to the + * given workspace path. + * @param resource the resource that will be copied + * @param destination the full workspace path of the destination the resource is being copied to + */ + public void copy(IResource resource, IPath destination); + + /** + * Record a delta that represents a resource being created. + * @param resource the resource that is created + */ + public void create(IResource resource); + + /** + * Record the set of deltas representing a deletion of the given resource. + * @param resource the resource that will be deleted + */ + public void delete(IResource resource); + + /** + * Return the proposed delta that has been accumulated by this factory. + * @return the proposed delta that has been accumulated by this factory + */ + public IResourceDelta getDelta(); + + /** + * Record the set of deltas representing a move of the given resource to the + * given workspace path. Note that this API is used to describe a resource + * being moved to another path in the workspace, rather than a move in the + * file system. + * @param resource the resource that will be moved + * @param destination the full workspace path of the destination the resource is being moved to + */ + public void move(IResource resource, IPath destination); + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java new file mode 100644 index 0000000000..48b97c5c91 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.*; +import org.eclipse.core.internal.resources.mapping.ModelProviderManager; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * Represents the provider of a logical model. The main purpose of this + * API is to support batch operations on sets of ResourceMapping + * objects that are part of the same model. + * + *

                  + * This class may be subclassed by clients. + *

                  + * @see org.eclipse.core.resources.mapping.ResourceMapping + * @since 3.2 + */ +public abstract class ModelProvider extends PlatformObject { + /** + * The model provider id of the Resources model. + */ + public static final String RESOURCE_MODEL_PROVIDER_ID = "org.eclipse.core.resources.modelProvider"; //$NON-NLS-1$ + + private IModelProviderDescriptor descriptor; + + /** + * Returns the descriptor for the model provider of the given id + * or null if the provider has not been registered. + * @param id a model provider id. + * @return the descriptor for the model provider of the given id + * or null if the provider has not been registered + */ + public static IModelProviderDescriptor getModelProviderDescriptor(String id) { + IModelProviderDescriptor[] descs = ModelProviderManager.getDefault().getDescriptors(); + for (int i = 0; i < descs.length; i++) { + IModelProviderDescriptor descriptor = descs[i]; + if (descriptor.getId().equals(id)) { + return descriptor; + } + } + return null; + } + + /** + * Returns the descriptors for all model providers that are registered. + * + * @return the descriptors for all model providers that are registered. + */ + public static IModelProviderDescriptor[] getModelProviderDescriptors() { + return ModelProviderManager.getDefault().getDescriptors(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ModelProvider) { + ModelProvider other = (ModelProvider) obj; + return other.getDescriptor().getId().equals(getDescriptor().getId()); + } + return super.equals(obj); + } + + /** + * Returns the descriptor of this model provider. The descriptor + * is set during initialization so implements cannot call this method + * until after the initialize method is invoked. + * @return the descriptor of this model provider + */ + public final IModelProviderDescriptor getDescriptor() { + return descriptor; + } + + /** + * Returns the unique identifier of this model provider. + *

                  + * The model provider identifier is composed of the model provider's + * plug-in id and the simple id of the provider extension. For example, if + * plug-in "com.xyz" defines a provider extension with id + * "myModelProvider", the unique model provider identifier will be + * "com.xyz.myModelProvider". + *

                  + * + * @return the unique model provider identifier + */ + public final String getId() { + return descriptor.getId(); + } + + /** + * Returns the resource mappings that cover the given resource. + * By default, an empty array is returned. Subclass may override + * this method but should consider overriding either + * {@link #getMappings(IResource[], ResourceMappingContext, IProgressMonitor)} + * or {@link #getMappings(ResourceTraversal[], ResourceMappingContext, IProgressMonitor)} + * if more context is needed to determine the proper mappings. + * + * @param resource the resource + * @param context a resource mapping context + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the resource mappings that cover the given resource. + * @exception CoreException + */ + public ResourceMapping[] getMappings(IResource resource, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + return new ResourceMapping[0]; + } + + /** + * Returns the set of mappings that cover the given resources. + * This method is used to map operations on resources to + * operations on resource mappings. By default, this method + * calls getMapping(IResource) for each resource. + *

                  + * Subclasses may override this method. + *

                  + * + * @param resources the resources + * @param context a resource mapping context + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the set of mappings that cover the given resources + * @exception CoreException + */ + public ResourceMapping[] getMappings(IResource[] resources, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + Set mappings = new HashSet<>(); + for (int i = 0; i < resources.length; i++) { + IResource resource = resources[i]; + ResourceMapping[] resourceMappings = getMappings(resource, context, monitor); + if (resourceMappings.length > 0) + mappings.addAll(Arrays.asList(resourceMappings)); + } + return mappings.toArray(new ResourceMapping[mappings.size()]); + } + + /** + * Returns the set of mappings that overlap with the given resource traversals. + * This method is used to map operations on resources to + * operations on resource mappings. By default, this method + * calls {@link #getMappings(IResource[], ResourceMappingContext, IProgressMonitor)} + * with the resources extracted from each traversal. + *

                  + * Subclasses may override this method. + *

                  + * + * @param traversals the traversals + * @param context a resource mapping context + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the set of mappings that overlap with the given resource traversals + */ + public ResourceMapping[] getMappings(ResourceTraversal[] traversals, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + Set result = new HashSet<>(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + ResourceMapping[] mappings = getMappings(traversal.getResources(), context, monitor); + for (int j = 0; j < mappings.length; j++) + result.add(mappings[j]); + } + return result.toArray(new ResourceMapping[result.size()]); + } + + /** + * Returns a set of traversals that cover the given resource mappings. The + * provided mappings must be from this provider or one of the providers this + * provider extends. + *

                  + * The default implementation accumulates the traversals from the given + * mappings. Subclasses can override to provide a more optimal + * transformation. + *

                  + * + * @param mappings the mappings being mapped to resources + * @param context the context used to determine the set of traversals that + * cover the mappings + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a set of traversals that cover the given mappings + * @exception CoreException + */ + public ResourceTraversal[] getTraversals(ResourceMapping[] mappings, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + SubMonitor subMonitor = SubMonitor.convert(monitor, mappings.length); + List traversals = new ArrayList<>(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + Collections.addAll(traversals, mapping.getTraversals(context, subMonitor.newChild(1))); + } + return traversals.toArray(new ResourceTraversal[traversals.size()]); + } + + @Override + public int hashCode() { + return getDescriptor().getId().hashCode(); + } + + /** + * This method is called by the model provider framework when the model + * provider is instantiated. This method should not be called by clients and + * cannot be overridden by subclasses. However, it invokes the + * initialize method once the descriptor is set so subclasses + * can override that method if they need to do additional initialization. + * + * @param desc the description of the provider as it appears in the plugin manifest + * @noreference This method is not intended to be referenced by clients. + */ + public final void init(IModelProviderDescriptor desc) { + if (descriptor != null) { + // prevent subsequent calls from damaging this instance + return; + } + descriptor = desc; + initialize(); + } + + /** + * Initialization method that is called after the descriptor + * of this provider is set. Subclasses may override. + */ + protected void initialize() { + // Do nothing + } + + /** + * Validates the proposed changes contained in the given delta. + *

                  + * This method must return either a {@link ModelStatus}, or a {@link MultiStatus} + * whose children are {@link ModelStatus}. The severity of the returned status + * indicates the severity of the possible side-effects of the operation. Any + * severity other than OK will be shown to the user. The + * message should be a human readable message that will allow the user to + * make a decision on whether to continue with the operation. The model + * provider id should indicate which model is flagging the possible side effects. + *

                  + * This default implementation accepts all changes and returns a status with + * severity OK. Subclasses should override to perform + * validation specific to their model. + *

                  + * + * @param delta a delta tree containing the proposed changes + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status indicating any potential side effects + * on the model that provided this validator. + */ + public IStatus validateChange(IResourceDelta delta, IProgressMonitor monitor) { + return new ModelStatus(IStatus.OK, ResourcesPlugin.PI_RESOURCES, descriptor.getId(), Status.OK_STATUS.getMessage()); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java new file mode 100644 index 0000000000..95fe5abc7f --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 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.resources.mapping; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.Status; + +/** + * A status returned by a model from the resource operation validator. + * The severity indicates the severity of the possible side effects + * of the operation. Any severity other than OK should be + * shown to the user. The message should be a human readable message that + * will allow the user to make a decision as to whether to continue with the + * operation. The model provider id should indicate which model is flagging the + * the possible side effects. + *

                  + * Clients may instantiate or subclass this class. + *

                  + * + * @since 3.2 + */ +public class ModelStatus extends Status { + + private final String modelProviderId; + + /** + * Create a model status. + * + * @param severity the severity + * @param pluginId the plugin id + * @param modelProviderId the model provider id + * @param message the message + */ + public ModelStatus(int severity, String pluginId, String modelProviderId, String message) { + super(severity, pluginId, 0, message, null); + Assert.isNotNull(modelProviderId); + this.modelProviderId = modelProviderId; + } + + /** + * Return the id of the model provider from which this status originated. + * + * @return the id of the model provider from which this status originated + */ + public String getModelProviderId() { + return modelProviderId; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java new file mode 100644 index 0000000000..227b8508ce --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java @@ -0,0 +1,321 @@ +/******************************************************************************* + * 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.resources.mapping; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A remote mapping context provides a model element with a view of the remote + * state of local resources as they relate to a repository operation that is in + * progress. A repository provider can pass an instance of this interface to a + * model element when obtaining a set of traversals for a model element. This + * allows the model element to query the remote state of a resource in order to + * determine if there are resources that exist remotely but do not exist locally + * that should be included in the traversal. + *

                  + * This class may be subclassed by clients. + *

                  + * + * @see ResourceMapping + * @see ResourceMappingContext + * @since 3.2 + */ +public abstract class RemoteResourceMappingContext extends ResourceMappingContext { + + /** + * Refresh flag constant (bit mask value 1) indicating that the mapping will + * be making use of the contents of the files covered by the traversals + * being refreshed. + */ + public static final int FILE_CONTENTS_REQUIRED = 1; + + /** + * Refresh flag constant (bit mask value 0) indicating that no additional + * refresh behavior is required. + */ + public static final int NONE = 0; + + /** + * For three-way comparisons, returns an instance of IStorage in order to + * allow the caller to access the contents of the base resource that + * corresponds to the given local resource. The base of a resource is the + * contents of the resource before any local modifications were made. If the + * base file does not exist, or if this is a two-way comparison, null + * is returned. The provided local file handle need not exist locally. A exception + * is thrown if the corresponding base resource is not a file. + *

                  + * This method may be long running as a server may need to be contacted to + * obtain the contents of the file. + *

                  + * + * @param file the local file + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a storage that provides access to the contents of the local + * resource's corresponding remote resource. If the remote file does not + * exist, null is returned + * @exception CoreException if the contents could not be fetched. Reasons + * include: + *
                    + *
                  • The server could not be contacted for some reason. + *
                  • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                  • + *
                  + */ + public abstract IStorage fetchBaseContents(IFile file, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the members of the base resource corresponding to the given container. + * The container and the returned members need not exist locally and may not + * include all children that exist locally. An empty list is returned if the base resource + * is empty or does not exist. An exception is thrown if the base resource is not + * capable of having members. This method returns null if + * the base members cannot be computed, in which case clients should call + * {@link #fetchMembers(IContainer, IProgressMonitor)} which returns the + * combined members for the base and remote. + *

                  + *

                  + * This method may be long running as a server may need to be contacted to + * obtain the members of the base resource. + *

                  + *

                  + * This default implementation always returns null, but subclasses + * may override. + *

                  + * @param container the local container + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the members of the base resource corresponding to the given container + * @exception CoreException if the members could not be fetched. Reasons + * include: + *
                    + *
                  • The server could not be contacted for some reason.
                  • + *
                  • The base resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                  • + *
                  + * @since 3.3 + */ + public IResource[] fetchBaseMembers(IContainer container, IProgressMonitor monitor) throws CoreException { + //default implementation does nothing + //thwart compiler warning + return null; + } + + /** + * Returns the combined members of the base and remote resources corresponding + * to the given container. The container need not exist locally and the result may + * include entries that do not exist locally and may not include all local + * children. An empty list is returned if the remote resource which + * corresponds to the container is empty or if the remote does not exist. An + * exception is thrown if the corresponding remote is not capable of having + * members. + *

                  + * This method may be long running as a server may need to be contacted to + * obtain the members of the container's corresponding remote resource. + *

                  + * + * @param container the local container + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return returns the combined members of the base and remote resources + * corresponding to the given container. + * @exception CoreException if the members could not be fetched. Reasons + * include: + *
                    + *
                  • The server could not be contacted for some reason.
                  • + *
                  • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                  • + *
                  + */ + public abstract IResource[] fetchMembers(IContainer container, IProgressMonitor monitor) throws CoreException; + + /** + * Returns an instance of IStorage in order to allow the caller to access + * the contents of the remote that corresponds to the given local resource. + * If the remote file does not exist, null is returned. The + * provided local file handle need not exist locally. A exception is thrown + * if the corresponding remote resource is not a file. + *

                  + * This method may be long running as a server may need to be contacted to + * obtain the contents of the file. + *

                  + * + * @param file the local file + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a storage that provides access to the contents of the local + * resource's corresponding remote resource. If the remote file does not + * exist, null is returned + * @exception CoreException if the contents could not be fetched. Reasons + * include: + *
                    + *
                  • The server could not be contacted for some reason.
                  • + *
                  • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                  • + *
                  + */ + public abstract IStorage fetchRemoteContents(IFile file, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the members of the remote resource corresponding to the given container. + * The container and the returned members need not exist locally and may not + * include all children that exist locally. An empty list is returned if the remote resource + * is empty or does not exist. An exception is thrown if the remote resource is not + * capable of having members. This method returns null if + * the remote members cannot be computed, in which case clients should call + * {@link #fetchMembers(IContainer, IProgressMonitor)} which returns the + * combined members for the base and remote. + *

                  + *

                  + * This method may be long running as a server may need to be contacted to + * obtain the members of the remote resource. + *

                  + *

                  + * This default implementation always returns null, but subclasses + * may override. + *

                  + * @param container the local container + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the members of the remote resource corresponding to the given container + * @exception CoreException if the members could not be fetched. Reasons + * include: + *
                    + *
                  • The server could not be contacted for some reason.
                  • + *
                  • The remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                  • + *
                  + * @since 3.3 + */ + public IResource[] fetchRemoteMembers(IContainer container, IProgressMonitor monitor) throws CoreException { + //default implementation does nothing + //thwart compiler warning + return null; + } + + /** + * Return the list of projects that apply to this context. + * In other words, the context is only capable of querying the + * remote state for projects that are contained in the + * returned list. + * @return the list of projects that apply to this context + */ + public abstract IProject[] getProjects(); + + /** + * For three-way comparisons, this method indicates whether local + * modifications have been made to the given resource. For two-way + * comparisons, calling this method has the same effect as calling + * {@link #hasRemoteChange(IResource, IProgressMonitor)}. + * + * @param resource the resource being tested + * @param monitor a progress monitor + * @return whether the resource contains local modifications + * @exception CoreException if the contents could not be compared. Reasons + * include: + *
                    + *
                  • The server could not be contacted for some reason.
                  • + *
                  • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                  • + *
                  + */ + public abstract boolean hasLocalChange(IResource resource, IProgressMonitor monitor) throws CoreException; + + /** + * For two-way comparisons, return whether the contents of the corresponding + * remote differs from the content of the local file in the context of the + * current operation. By this we mean that this method will return + * true if the remote contents differ from the local + * contents. + *

                  + * For three-way comparisons, return whether the contents of the + * corresponding remote differ from the contents of the base. In other + * words, this method returns true if the corresponding + * remote has changed since the last time the local resource was updated + * with the remote contents. + *

                  + * For two-way comparisons, return true if the remote + * contents differ from the local contents. In this case, this method is + * equivalent to {@link #hasLocalChange(IResource, IProgressMonitor)} + *

                  + * This can be used by clients to determine if they need to fetch the remote + * contents in order to determine if the resources that constitute the model + * element are different in the remote location. If the local file exists + * and the remote file does not, or the remote file exists and the local + * does not then the contents will be said to differ (i.e. true + * is returned). Also, implementors will most likely use a timestamp based + * comparison to determine if the contents differ. This may lead to a + * situation where true is returned but the actual contents + * do not differ. Clients must be prepared handle this situation. + *

                  + * + * @param resource the local resource + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return whether the contents of the corresponding remote differ from the + * base. + * @exception CoreException if the contents could not be compared. Reasons + * include: + *
                    + *
                  • The server could not be contacted for some reason.
                  • + *
                  • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                  • + *
                  + */ + public abstract boolean hasRemoteChange(IResource resource, IProgressMonitor monitor) throws CoreException; + + /** + * Return true if the context is associated with an operation + * that is using a three-way comparison and false if it is + * using a two-way comparison. + * + * @return whether the context is a three-way or two-way + */ + public abstract boolean isThreeWay(); + + /** + * Refresh the known remote state for any resources covered by the given + * traversals. Clients who require the latest remote state should invoke + * this method before invoking any others of the class. Mappings can use + * this method as a hint to the context provider of which resources will be + * required for the mapping to generate the proper set of traversals. + *

                  + * Note that this is really only a hint to the context provider. It is up to + * implementors to decide, based on the provided traversals, how to + * efficiently perform the refresh. In the ideal case, calls to + * {@link #hasRemoteChange(IResource, IProgressMonitor)} and + * {@link #fetchMembers} would not need to contact the server after a call to a + * refresh with appropriate traversals. Also, ideally, if + * {@link #FILE_CONTENTS_REQUIRED} is on of the flags, then the contents + * for these files will be cached as efficiently as possible so that calls to + * {@link #fetchRemoteContents} will also not need to contact the server. This + * may not be possible for all context providers, so clients cannot assume that + * the above mentioned methods will not be long running. It is still advisable + * for clients to call {@link #refresh} with as much details as possible since, in + * the case where a provider is optimized, performance will be much better. + *

                  + * + * @param traversals the resource traversals that indicate which resources + * are to be refreshed + * @param flags additional refresh behavior. For instance, if + * {@link #FILE_CONTENTS_REQUIRED} is one of the flags, this indicates + * that the client will be accessing the contents of the files covered by + * the traversals. {@link #NONE} should be used when no additional + * behavior is required + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the refresh fails. Reasons include: + *
                    + *
                  • The server could not be contacted for some reason.
                  • + *
                  + */ + public abstract void refresh(ResourceTraversal[] traversals, int flags, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java new file mode 100644 index 0000000000..6278a8b1b5 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2006, 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.ArrayList; +import java.util.List; +import org.eclipse.core.internal.resources.mapping.ChangeDescription; +import org.eclipse.core.internal.resources.mapping.ResourceChangeDescriptionFactory; +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 resource change validator is used to validate that changes made to + * resources will not adversely affect the models stored in those resources. + *

                  + * The validator is used by first creating a resource delta describing the + * proposed changes. A delta can be generated using a {@link IResourceChangeDescriptionFactory}. + * The change is then validated by calling the {@link #validateChange(IResourceDelta, IProgressMonitor)} + * method. This example validates a change to a single file: + * + * IFile file = ..;//some file that is going to be changed + * ResourceChangeValidator validator = ResourceChangeValidator.getValidator(); + * IResourceChangeDescriptionFactory factory = validator.createDeltaFactory(); + * factory.change(file); + * IResourceDelta delta = factory.getDelta(); + * IStatus result = validator.validateChange(delta, null); + * + * If the result status does not have severity {@link IStatus#OK}, then + * the changes may cause problems for models that are built on those + * resources. In this case the user should be presented with the status message + * to determine if they want to proceed with the modification. + *

                  + * + * @since 3.2 + */ +public final class ResourceChangeValidator { + private static ResourceChangeValidator instance; + + /** + * Return the singleton change validator. + * @return the singleton change validator + */ + public static ResourceChangeValidator getValidator() { + if (instance == null) + instance = new ResourceChangeValidator(); + return instance; + } + + /** + * Singleton accessor method should be used instead. + * @see #getValidator() + */ + private ResourceChangeValidator() { + super(); + } + + private IStatus combineResults(IStatus[] result) { + List notOK = new ArrayList<>(); + for (int i = 0; i < result.length; i++) { + IStatus status = result[i]; + if (!status.isOK()) { + notOK.add(status); + } + } + if (notOK.isEmpty()) { + return Status.OK_STATUS; + } + if (notOK.size() == 1) { + return notOK.get(0); + } + return new MultiStatus(ResourcesPlugin.PI_RESOURCES, 0, notOK.toArray(new IStatus[notOK.size()]), Messages.mapping_multiProblems, null); + } + + /** + * Return an empty change description factory that can be used to build a + * proposed resource delta. + * @return an empty change description factory that can be used to build a + * proposed resource delta + */ + public IResourceChangeDescriptionFactory createDeltaFactory() { + return new ResourceChangeDescriptionFactory(); + } + + private ModelProvider[] getProviders(IResource[] resources) { + IModelProviderDescriptor[] descriptors = ModelProvider.getModelProviderDescriptors(); + List result = new ArrayList<>(); + for (int i = 0; i < descriptors.length; i++) { + IModelProviderDescriptor descriptor = descriptors[i]; + try { + IResource[] matchingResources = descriptor.getMatchingResources(resources); + if (matchingResources.length > 0) { + result.add(descriptor.getModelProvider()); + } + } catch (CoreException e) { + Policy.log(e.getStatus().getSeverity(), NLS.bind("Could not instantiate provider {0}", descriptor.getId()), e); //$NON-NLS-1$ + } + } + return result.toArray(new ModelProvider[result.size()]); + } + + /* + * Get the roots of any changes. + */ + private IResource[] getRootResources(IResourceDelta root) { + final ChangeDescription changeDescription = new ChangeDescription(); + try { + root.accept(new IResourceDeltaVisitor() { + @Override + public boolean visit(IResourceDelta delta) { + return changeDescription.recordChange(delta); + } + }); + } catch (CoreException e) { + // Shouldn't happen since the ProposedResourceDelta accept doesn't throw an + // exception and our visitor doesn't either + Policy.log(IStatus.ERROR, "Internal error", e); //$NON-NLS-1$ + } + return changeDescription.getRootResources(); + } + + /** + * Validate the proposed changes contained in the given delta + * by consulting all model providers to determine if the changes + * have any adverse side effects. + *

                  + * This method returns either a {@link ModelStatus}, or a {@link MultiStatus} + * whose children are {@link ModelStatus}. In either case, the severity + * of the status indicates the severity of the possible side-effects of + * the operation. Any severity other than OK should be + * shown to the user. The message should be a human readable message that + * will allow the user to make a decision on whether to continue with the + * operation. The model provider id should indicate which model is flagging the + * the possible side effects. + *

                  + * + * @param delta a delta tree containing the proposed changes + * @return a status indicating any potential side effects + * on models stored in the affected resources. + */ + public IStatus validateChange(IResourceDelta delta, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + IResource[] resources = getRootResources(delta); + ModelProvider[] providers = getProviders(resources); + if (providers.length == 0) + return Status.OK_STATUS; + monitor.beginTask(Messages.mapping_validate, providers.length); + IStatus[] result = new IStatus[providers.length]; + for (int i = 0; i < providers.length; i++) + result[i] = providers[i].validateChange(delta, Policy.subMonitorFor(monitor, 1)); + return combineResults(result); + } finally { + monitor.done(); + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java new file mode 100644 index 0000000000..725486090f --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.ArrayList; +import org.eclipse.core.internal.resources.mapping.ModelProviderManager; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * A resource mapping supports the transformation of an application model + * object into its underlying file system resources. It provides the + * bridge between a logical element and the physical resource(s) into which it + * is stored but does not provide more comprehensive model access or + * manipulations. + *

                  + * Mappings provide two means of model traversal. The {@link #accept} method + * can be used to visit the resources that constitute the model object. Alternatively, + * a set or traversals can be obtained by calling {@link #getTraversals}. A traversal + * contains a set of resources and a depth. This allows clients (such a repository providers) + * to do optimal traversals of the resources w.r.t. the operation that is being performed + * on the model object. + *

                  + *

                  + * This class may be subclassed by clients. + *

                  + + * @see IResource + * @see ResourceTraversal + * @since 3.2 + */ +public abstract class ResourceMapping extends PlatformObject { + + /** + * Accepts the given visitor for all existing resources in this mapping. + * The visitor's {@link IResourceVisitor#visit} method is called for each + * accessible resource in this mapping. + * + * @param context the traversal context + * @param visitor the visitor + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                    + *
                  • The visitor failed with this exception.
                  • + *
                  • The traversals for this mapping could not be obtained.
                  • + *
                  + */ + public void accept(ResourceMappingContext context, IResourceVisitor visitor, IProgressMonitor monitor) throws CoreException { + ResourceTraversal[] traversals = getTraversals(context, monitor); + for (int i = 0; i < traversals.length; i++) + traversals[i].accept(visitor); + } + + /** + * Return whether this resource mapping contains all the resources + * of the given mapping. + *

                  + * This method always returns false when the given resource + * mapping's model provider id does not match that the of the receiver. + *

                  + * + * @param mapping the given resource mapping + * @return true if this mapping contains all the resources + * of the given mapping, and false otherwise. + */ + public boolean contains(ResourceMapping mapping) { + return false; + } + + /** + * Override equals to compare the model objects of the + * mapping in order to determine equality. + * @param obj the object to compare + * @return true if the receiver is equal to the + * given object, and false otherwise. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj instanceof ResourceMapping) { + ResourceMapping other = (ResourceMapping) obj; + return other.getModelObject().equals(getModelObject()); + } + return false; + } + + /** + * Returns all markers of the specified type on the resources in this mapping. + * If includeSubtypes is false, only markers + * whose type exactly matches the given type are returned. Returns an empty + * array if there are no matching markers. + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return an array of markers + * @exception CoreException if this method fails. + */ + public IMarker[] findMarkers(String type, boolean includeSubtypes, IProgressMonitor monitor) throws CoreException { + final ResourceTraversal[] traversals = getTraversals(ResourceMappingContext.LOCAL_CONTEXT, monitor); + ArrayList result = new ArrayList<>(); + for (int i = 0; i < traversals.length; i++) + traversals[i].doFindMarkers(result, type, includeSubtypes); + return result.toArray(new IMarker[result.size()]); + } + + /** + * Returns the application model element associated with this + * resource mapping. + * + * @return the application model element associated with this + * resource mapping. + */ + public abstract Object getModelObject(); + + /** + * Return the model provider for the model object + * of this resource mapping. The model provider is obtained + * using the id returned from getModelProviderId(). + * @return the model provider + */ + public final ModelProvider getModelProvider() { + try { + return ModelProviderManager.getDefault().getModelProvider(getModelProviderId()); + } catch (CoreException e) { + throw new IllegalStateException(e.getMessage()); + } + } + + /** + * Returns the id of the model provider that generated this resource + * mapping. + * + * @return the model provider id + */ + public abstract String getModelProviderId(); + + /** + * Returns the projects that contain the resources that constitute this + * application model. + * + * @return the projects + */ + public abstract IProject[] getProjects(); + + /** + * Returns one or more traversals that can be used to access all the + * physical resources that constitute the logical resource. A traversal is + * simply a set of resources and the depth to which they are to be + * traversed. This method returns an array of traversals in order to provide + * flexibility in describing the traversals that constitute a model element. + *

                  + * Subclasses should, when possible, include + * all resources that are or may be members of the model element. + * For instance, a model element should return the same list of + * resources regardless of the existence of the files on the file system. + * For example, if a logical resource called "form" maps to "/p1/form.xml" + * and "/p1/form.java" then whether form.xml or form.java existed, they + * should be returned by this method. + *

                  + * In some cases, it may not be possible for a model element to know all the + * resources that may constitute the element without accessing the state of + * the model element in another location (e.g. a repository). This method is + * provided with a context which, when provided, gives access to + * the members of corresponding remote containers and the contents of + * corresponding remote files. This gives the model element the opportunity + * to deduce what additional resources should be included in the traversal. + *

                  + * + * @param context gives access to the state of + * remote resources that correspond to local resources for the + * purpose of determining traversals that adequately cover the + * model element resources given the state of the model element + * in another location. This parameter may be null, in + * which case the implementor can assume that only the local + * resources are of interest to the client. + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a set of traversals that cover the resources that constitute the + * model element + * @exception CoreException if the traversals could not be obtained. + */ + public abstract ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) throws CoreException; + + /** + * Override hashCode to use the model object. + */ + @Override + public int hashCode() { + return getModelObject().hashCode(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java new file mode 100644 index 0000000000..c0a0cae230 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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 Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +/** + * A resource mapping context is provided to a resource mapping when traversing + * the resources of the mapping. The type of context may determine what resources + * are included in the traversals of a mapping. + *

                  + * There are currently two resource mapping contexts: the local mapping context + * (represented by the singleton {@link #LOCAL_CONTEXT}), + * and {@link RemoteResourceMappingContext}. Implementors of {@link ResourceMapping} + * should not assume that these are the only valid contexts (in order to allow future + * extensibility). Therefore, if the provided context is not of one of the above mentioned types, + * the implementor can assume that the context is a local context. + *

                  + *

                  + * This class may be subclassed by clients; this class is not intended to be + * instantiated directly. + *

                  + * + * @see ResourceMapping + * @see RemoteResourceMappingContext + * @since 3.2 + */ +public class ResourceMappingContext { + + /** + * This resource mapping context is used to indicate that the operation + * that is requesting the traversals is performing a local operation. + * Because the operation is local, the resource mapping is free to be + * as precise as desired about what resources make up the mapping without + * concern for performing optimized remote operations. + */ + public static final ResourceMappingContext LOCAL_CONTEXT = new ResourceMappingContext(); + +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java new file mode 100644 index 0000000000..8e244d6d17 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java @@ -0,0 +1,183 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.ArrayList; +import org.eclipse.core.internal.resources.MarkerManager; +import org.eclipse.core.internal.resources.Workspace; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; + +/** + * A resource traversal is simply a set of resources and the depth to which + * each is to be traversed. A set of traversals is used to describe the + * resources that constitute a model element. + *

                  + * The flags of the traversal indicate which special resources should be + * included or excluded from the traversal. The flags used are the same as + * those passed to the {@link IResource#accept(IResourceVisitor, int, int)} method. + * + *

                  + * This class may be instantiated or subclassed by clients. + *

                  + + * @see org.eclipse.core.resources.IResource + * @since 3.2 + */ +public class ResourceTraversal { + + private final int depth; + private final int flags; + private final IResource[] resources; + + /** + * Creates a new resource traversal. + * @param resources The resources in the traversal + * @param depth The traversal depth + * @param flags the flags for this traversal. The traversal flags match those + * that are passed to the IResource#accept method. + */ + public ResourceTraversal(IResource[] resources, int depth, int flags) { + if (resources == null) + throw new NullPointerException(); + this.resources = resources; + this.depth = depth; + this.flags = flags; + } + + /** + * Visits all existing resources defined by this traversal. + * + * @param visitor a resource visitor + * @exception CoreException if this method fails. Reasons include: + *
                    + *
                  • The visitor failed with this exception.
                  • + *
                  + */ + public void accept(IResourceVisitor visitor) throws CoreException { + for (int i = 0, imax = resources.length; i < imax; i++) + try { + if (resources[i].exists()) + resources[i].accept(visitor, depth, flags); + } catch (CoreException e) { + //ignore failure in the case of concurrent deletion + if (e.getStatus().getCode() != IResourceStatus.RESOURCE_NOT_FOUND) + throw e; + } + } + + /** + * Return whether the given resource is contained in or + * covered by this traversal, regardless of whether the resource + * currently exists. + * + * @param resource the resource to be tested + * @return true if the resource is in this traversal, and + * false otherwise. + */ + public boolean contains(IResource resource) { + for (int i = 0; i < resources.length; i++) { + IResource member = resources[i]; + if (contains(member, resource)) { + return true; + } + } + return false; + } + + private boolean contains(IResource resource, IResource child) { + if (resource.equals(child)) + return true; + if (depth == IResource.DEPTH_ZERO) + return false; + if (child.getParent().equals(resource)) + return true; + if (depth == IResource.DEPTH_INFINITE) + return resource.getFullPath().isPrefixOf(child.getFullPath()); + return false; + } + + /** + * Efficient implementation of {@link #findMarkers(String, boolean)}, not + * available to clients because underlying non-API methods are used that + * may change. + */ + void doFindMarkers(ArrayList result, String type, boolean includeSubtypes) { + MarkerManager markerMan = ((Workspace) ResourcesPlugin.getWorkspace()).getMarkerManager(); + for (int i = 0; i < resources.length; i++) + markerMan.doFindMarkers(resources[i], result, type, includeSubtypes, depth); + } + + /** + * Returns all markers of the specified type on existing resources in this traversal. + * If includeSubtypes is false, only markers + * whose type exactly matches the given type are returned. Returns an empty + * array if there are no matching markers. + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @return an array of markers + * @exception CoreException if this method fails. + * @see IResource#findMarkers(String, boolean, int) + */ + public IMarker[] findMarkers(String type, boolean includeSubtypes) throws CoreException { + if (resources.length == 0) + return new IMarker[0]; + ArrayList result = new ArrayList<>(); + doFindMarkers(result, type, includeSubtypes); + return result.toArray(new IMarker[result.size()]); + } + + /** + * Returns the depth to which the resources should be traversed. + * + * @return the depth to which the physical resources are to be traversed + * (one of IResource.DEPTH_ZERO, IResource.DEPTH_ONE or + * IResource.DEPTH_INFINITE) + */ + public int getDepth() { + return depth; + } + + /** + * Return the flags for this traversal. + * The flags of the traversal indicate which special resources should be + * included or excluded from the traversal. The flags used are the same as + * those passed to the IResource#accept(IResourceVisitor, int, int) method. + * Clients who traverse the resources manually (i.e. without calling accept) + * should respect the flags when determining which resources are included + * in the traversal. + * + * @return the flags for this traversal + */ + public int getFlags() { + return flags; + } + + /** + * Returns the file system resource(s) for this traversal. The returned + * resources must be contained within the same project and need not exist in + * the local file system. The traversal of the returned resources should be + * done considering the flag returned by getDepth. If a resource returned by + * a traversal is a file, it should always be visited. If a resource of a + * traversal is a folder then files contained in the folder can only be + * visited if the folder is IResource.DEPTH_ONE or IResource.DEPTH_INFINITE. + * Child folders should only be visited if the depth is + * IResource.DEPTH_INFINITE. + * + * @return The resources in this traversal + */ + public IResource[] getResources() { + return resources; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html new file mode 100644 index 0000000000..c3214612a1 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html @@ -0,0 +1,27 @@ + + + + + + + Package-level Javadoc + + +Provides APIs for integrating application models with the workspace + +

                  Package Specification

                  +

                  +This package specifies the APIs in the Resources plug-in that are used to integrate +application models with the workspace. This API introduces the notion of a +ResourceMapping that defines the relationship between an application +model object and a set of underlying resources, and a ResourceTraversal +that describes the exact resources corresponding to a given application model object. +The relationship between an application model and underlying resources can vary +depending a context. This notion is captured by ResourceMappingContext +and its subclasses. +

                  +@since 3.2 +

                  + + + diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html new file mode 100644 index 0000000000..6fe0b74733 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html @@ -0,0 +1,25 @@ + + + + + + + Package-level Javadoc + + +Provides basic support for managing a workspace and +its resources. +

                  +Package Specification

                  +This package specifies the principal API for the Resources plug-in.  +The resources plug-in defines the notions of Workspaces and Resources.  +The workspace's resource model is very similar to a file system.  +All resources are backed by a real file or directory in some backing file +system.  They are stored in their native form (i.e., no extra bytes +or markup) using their normal names. +

                  In addition to basic resource management, the Resources plug-in supports +various workspace lifecycle events such as save and snapshot, and resource +change events. +
                    + + diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java new file mode 100644 index 0000000000..221ed6d40e --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 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.resources.refresh; + +import org.eclipse.core.resources.IResource; + +/** + * An IRefreshMonitor monitors trees of IResources + * for changes in the local file system. + *

                  + * When an IRefreshMonitor notices changes, it should report them + * to the IRefreshResult provided at the time of the monitor's + * creation. + *

                  + * Clients may implement this interface. + *

                  + * + * @since 3.0 + */ +public interface IRefreshMonitor { + /** + * Informs the monitor that it should stop monitoring the given resource. + * + * @param resource the resource that should no longer be monitored, or + * null if this monitor should stop monitoring all resources + * it is currently monitoring + */ + public void unmonitor(IResource resource); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java new file mode 100644 index 0000000000..5143dabca3 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 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.resources.refresh; + +import org.eclipse.core.resources.IResource; + +/** + * An IRefreshResult is provided to an auto-refresh + * monitor. The result is used to submit resources to be refreshed, and + * for reporting failure of the monitor. + * + * @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 IRefreshResult { + /** + * Notifies that the given monitor has encountered a failure from which it + * cannot recover while monitoring the given resource. + *

                  + * If the given resource is null it indicates that the + * monitor has failed completely, and the refresh manager will have to + * take over the monitoring responsibilities for all resources that the + * monitor was monitoring. + * + * @param monitor a monitor which has encountered a failure that it + * cannot recover from + * @param resource the resource that the monitor can no longer + * monitor, or null to indicate that the monitor can no + * longer monitor any of the resources it was monitoring + */ + public void monitorFailed(IRefreshMonitor monitor, IResource resource); + + /** + * Requests that the provided resource be refreshed. The refresh will + * occur in the background during the next scheduled refresh. + * + * @param resource the resource to refresh + */ + public void refresh(IResource resource); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java new file mode 100644 index 0000000000..2865774710 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.resources.refresh; + +import org.eclipse.core.internal.refresh.InternalRefreshProvider; +import org.eclipse.core.resources.IResource; + +/** + * The abstract base class for all auto-refresh providers. This class provides + * the infrastructure for defining an auto-refresh provider and fulfills the + * contract specified by the org.eclipse.core.resources.refreshProviders + * standard extension point. + *

                  + * All auto-refresh providers must subclass this class. A + * RefreshProvider is responsible for creating + * IRefreshMonitor objects. The provider must decide if + * it is capable of monitoring the file, or folder and subtree under the path that is provided. + * + * @since 3.0 + */ +public abstract class RefreshProvider extends InternalRefreshProvider { + /** + * Creates a new refresh monitor that performs naive polling of the resource + * in the file system to detect changes. The returned monitor will immediately begin + * monitoring the specified resource root and report changes back to the workspace. + *

                  + * This default monitor can be returned by subclasses when + * installMonitor is called. + *

                  + * If the returned monitor is not immediately returned from the installMonitor + * method, then clients are responsible for telling the returned monitor to + * stop polling when it is no longer needed. The returned monitor can be told to + * stop working by invoking IRefreshMonitor.unmonitor(IResource). + * + * @param resource The resource to begin monitoring + * @return A refresh monitor instance + * @see #installMonitor(IResource, IRefreshResult) + */ + @Override + protected IRefreshMonitor createPollingMonitor(IResource resource) { + return super.createPollingMonitor(resource); + } + + /** + * Returns an IRefreshMonitor that will monitor a resource. If + * the resource is an IContainer the monitor will also + * monitor the subtree under the container. Returns null if + * this provider cannot create a monitor for the given resource. The + * provider may return the same monitor instance that has been provided for + * other resources. + *

                  + * The monitor should send results and failures to the provided refresh + * result. + * + * @param resource the resource to monitor + * @param result the result callback for notifying of failure or of resources that need + * refreshing + * @return a monitor on the resource, or null + * if the resource cannot be monitored + * @see #createPollingMonitor(IResource) + */ + public abstract IRefreshMonitor installMonitor(IResource resource, IRefreshResult result); + + /** + * Resets the installed monitors for the given resource. This will remove all + * existing monitors that are installed on the resource, and then ask all + * refresh providers to begin monitoring the resource again. + *

                  + * This method is intended to be used by refresh providers that need to change + * the refresh monitor that they previously used to monitor a resource. + * + * @param resource The resource to reset the monitors for + */ + @Override + public void resetMonitors(IResource resource) { + super.resetMonitors(resource); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html new file mode 100644 index 0000000000..4d3a2fe39a --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html @@ -0,0 +1,23 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the auto-refresh providers. + +

                  Package Specification

                  +

                  +This package specifies the APIs in the Resources plug-in that are intended +to be implemented and used by plug-ins using the refreshProviders extension point. +This extension point is used by plug-ins to notify the workspace of changes that +have occurred externally in the file system. +

                  +@since 3.0 +

                  + + + diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java new file mode 100644 index 0000000000..4e7d3c2d8a --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ +package org.eclipse.core.resources.team; + +import org.eclipse.core.resources.IWorkspace; + +/** + * A context that is used in conjunction with the {@link FileModificationValidator} + * to indicate that UI-based validation is desired. + *

                  + * This class is not intended to be instantiated or subclassed by clients. + * + * @see FileModificationValidator + * @since 3.3 + */ +public class FileModificationValidationContext { + + /** + * Constant that can be passed to {@link IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], Object)} + * to indicate that the caller does not have access to a UI context but would still + * like to have UI-based validation if possible. + */ + public static final FileModificationValidationContext VALIDATE_PROMPT = new FileModificationValidationContext(null); + + private final Object shell; + + /** + * Create a context with the given shell. + * + * @param shell the shell + */ + FileModificationValidationContext(Object shell) { + this.shell = shell; + } + + /** + * Return the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null if there is no UI context + * available (declared as an Object to avoid any direct references on the SWT component). + * If there is no shell, the {@link FileModificationValidator} may still perform + * UI-based validation if they can obtain a Shell from another source. + * @return the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null + */ + public Object getShell() { + return shell; + } +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java new file mode 100644 index 0000000000..e56bc2b5a9 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.resources.team; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IStatus; + +/** + * The file modification validator is a Team-related hook for pre-checking operations + * that modify the contents of files. + *

                  + * This class is used only in conjunction with the + * "org.eclipse.core.resources.fileModificationValidator" + * extension point. It is intended to be implemented only + * by the Eclipse Platform Team plug-in or by repository providers + * whose validator get invoked by Team. + *

                  + * @since 3.3 + */ +@SuppressWarnings("deprecation") +public abstract class FileModificationValidator implements IFileModificationValidator { + + /** + * Validates that the given files can be modified. The files must all exist + * in the workspace. The optional context object may be supplied if + * UI-based validation is required. If the context is null, the + * validator must attempt to perform the validation in a headless manner. + * The returned status is IStatus.OK if this validator + * believes the given file can be modified. Other return statuses indicate + * the reason why the individual files cannot be modified. + * + * @param files the files that are to be modified; these files must all exist in the workspace + * @param context the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null if there is no UI context (declared + * as an Object to avoid any direct references on the SWT component) + * @return a status object that is OK if things are fine, otherwise a status describing + * reasons why modifying the given files is not reasonable + * @see IWorkspace#validateEdit(IFile[], Object) + * @deprecated this method is part of the deprecated {@link IFileModificationValidator} + * interface. Clients should call {@link #validateEdit(IFile[], FileModificationValidationContext)} + * instead. + */ + @Deprecated + @Override + public final IStatus validateEdit(IFile[] files, Object context) { + FileModificationValidationContext validationContext; + if (context == null) + validationContext = null; + else if (context instanceof FileModificationValidationContext) + validationContext = (FileModificationValidationContext) context; + else + validationContext = new FileModificationValidationContext(context); + return validateEdit(files, validationContext); + } + + /** + * Validates that the given file can be saved. This method is called from + * IFile#setContents and IFile#appendContents + * before any attempt to write data to disk. The returned status is + * IStatus.OK if this validator believes the given file can be + * successfully saved. In all other cases the return value is a non-OK status. + * Note that a return value of IStatus.OK does not guarantee + * that the save will succeed. + * + * @param file the file that is to be modified; this file must exist in the workspace + * @return a status indicating whether or not it is reasonable to try writing to the given file; + * IStatus.OK indicates a save should be attempted. + * + * @see IFile#setContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + * @see IFile#appendContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public IStatus validateSave(IFile file) { + return validateEdit(new IFile[] {file}, (FileModificationValidationContext) null); + } + + /** + * Validates that the given files can be modified. The files must all exist + * in the workspace. The optional context may be supplied if + * UI-based validation is required. If the context is null, the + * validator must attempt to perform the validation in a headless manner. + * The returned status is IStatus.OK if this validator + * believes the given file can be modified. Other return statuses indicate + * the reason why the individual files cannot be modified. + * + * @param files the files that are to be modified; these files must all exist in the workspace + * @param context the context to aid in UI-based validation or null if the validation + * must be headless + * @return a status object that is OK if things are fine, otherwise a status describing + * reasons why modifying the given files is not reasonable + * @see IWorkspace#validateEdit(IFile[], Object) + */ + public abstract IStatus validateEdit(IFile[] files, FileModificationValidationContext context); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java new file mode 100644 index 0000000000..1567a3b2c7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java @@ -0,0 +1,383 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.team; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +/** + * Primary interface for hooking the implementation of + * IResource.move and IResource.delete. + *

                  + * This interface is intended to be implemented by the team component in + * conjunction with the org.eclipse.core.resources.moveDeleteHook + * standard extension point. Individual team providers may also implement this + * interface. It is not intended to be implemented by other clients. The methods + * defined on this interface are called from within the implementations of + * IResource.move and IResource.delete. They are not + * intended to be called from anywhere else. + *

                  + * + * @since 2.0 + */ +public interface IMoveDeleteHook { + + /** + * Implements IResource.delete(int,IProgressMonitor) where the + * receiver is a file. Returns true to accept responsibility + * for implementing this operation as per the API contract. + *

                  + * In broad terms, a full re-implementation should delete the file in the + * local file system and then call tree.deletedFile to complete + * the updating of the workspace resource tree to reflect this fact. If + * unsuccessful in deleting the file from the local file system, it + * should instead call tree.failed to report the reason for + * the failure. In either case, it should return true to + * indicate that the operation was attempted. The FORCE update + * flag needs to be honored: unless FORCE is specified, the + * implementation must use tree.isSynchronized to determine + * whether the file is in sync before attempting to delete it. + * The KEEP_HISTORY update flag needs to be honored as well; + * use tree.addToLocalHistory to capture the contents of the + * file before deleting it from the local file system. + *

                  + * An extending implementation should perform whatever pre-processing it + * needs to do and then call tree.standardDeleteFile to + * explicitly invoke the standard file deletion behavior, which deletes + * both the file from the local file system and updates the workspace + * resource tree. It should return true to indicate that the + * operation was attempted. + *

                  + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteFile and returning true. + *

                  + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                  + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param file the handle of the file to delete; the receiver of + * IResource.delete(int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this method + * attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public boolean deleteFile(IResourceTree tree, IFile file, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.delete(int,IProgressMonitor) where the + * receiver is a folder. Returns true to accept responsibility + * for implementing this operation as per the API contract. + *

                  + * In broad terms, a full re-implementation should delete the directory tree + * in the local file system and then call tree.deletedFolder to + * complete the updating of the workspace resource tree to reflect this fact. + * If unsuccessful in deleting the directory or any of its descendents from + * the local file system, it should instead call tree.failed to + * report each reason for failure. In either case it should return + * true to indicate that the operation was attempted. + * The FORCE update flag needs to be honored: unless + * FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the folder + * subtree is in sync before attempting to delete it. + * The KEEP_HISTORY update flag needs to be honored as well; + * use tree.addToLocalHistory to capture the contents of any + * files being deleted. + *

                  + * A partial re-implementation should perform whatever pre-processing it + * needs to do and then call tree.standardDeleteFolder to + * explicitly invoke the standard folder deletion behavior, which deletes + * both the folder and its descendents from the local file system and + * updates the workspace resource tree. It should return true + * to indicate that the operation was attempted. + *

                  + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteFolder and returning true. + *

                  + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                  + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param folder the handle of the folder to delete; the receiver of + * IResource.delete(int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public boolean deleteFolder(IResourceTree tree, IFolder folder, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.delete(int,IProgressMonitor) where the + * receiver is a project. Returns true to accept responsibility + * for implementing this operation as per the API contract. + *

                  + * In broad terms, a full re-implementation should delete the project content area in + * the local file system if required (the files of a closed project should be deleted + * only if the IResource.ALWAYS_DELETE_PROJECT_CONTENTS update + * flag is specified; the files of an open project should be deleted unless the + * the IResource.NEVER_DELETE_PROJECT_CONTENTS update flag is + * specified). It should then call tree.deletedProject to complete + * the updating of the workspace resource tree to reflect this fact. If unsuccessful + * in deleting the project's files from the local file system, it should instead call + * tree.failed to report the reason for the failure. In either case, it + * should return true to indicate that the operation was attempted. + * The FORCE update flag may need to be honored if the project is open: + * unless FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the project subtree is in + * sync before attempting to delete it. + * Note that local history is not maintained when a project is deleted, + * regardless of the setting of the KEEP_HISTORY update flag. + *

                  + * A partial re-implementation should perform whatever pre-processing it needs + * to do and then call tree.standardDeleteProject to explicitly + * invoke the standard project deletion behavior. It should return true + * to indicate that the operation was attempted. + *

                  + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteProject and returning true. + *

                  + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                  + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param project the handle of the project to delete; the receiver of + * IResource.delete(int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public boolean deleteProject(IResourceTree tree, IProject project, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.move(IPath,int,IProgressMonitor) where + * the receiver is a file. Returns true to accept + * responsibility for implementing this operation as per the API contract. + *

                  + * On entry to this hook method, the following is guaranteed about the + * workspace resource tree: the source file exists; the destination file + * does not exist; the container of the destination file exists and is + * accessible. In broad terms, a full re-implementation should move the file + * in the local file system and then call tree.moveFile to + * complete the updating of the workspace resource tree to reflect this + * fact. If unsuccessful in moving the file in the local file system, + * it should instead call tree.failed to report the reason for + * the failure. In either case, it should return true to + * indicate that the operation was attempted. + * The FORCE update flag needs to be honored: unless + * FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the file is in sync before + * attempting to move it. + * The KEEP_HISTORY update flag needs to be honored as well; use + * tree.addToLocalHistory to capture the contents of the file + * (naturally, this must be before moving the file from the local file system). + *

                  + * An extending implementation should perform whatever pre-processing it needs + * to do and then call tree.standardMoveFile to explicitly + * invoke the standard file moving behavior, which moves both the file in the + * local file system and updates the workspace resource tree. It should return + * true to indicate that the operation was attempted. + *

                  + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardMoveFile and returning true. + *

                  + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                  + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param source the handle of the file to move; the receiver of + * IResource.move(IPath,int,IProgressMonitor) + * @param destination the handle of where the file will move to; the handle + * equivalent of the first parameter to + * IResource.move(IPath,int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(org.eclipse.core.runtime.IPath,int,IProgressMonitor) + */ + public boolean moveFile(IResourceTree tree, IFile source, IFile destination, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.move(IPath,int,IProgressMonitor) where + * the receiver is a project. Returns true to accept + * responsibility for implementing this operation as per the API contract. + *

                  + * On entry to this hook method, the following is guaranteed about the + * workspace resource tree: the source folder exists; the destination folder + * does not exist; the container of the destination folder exists and is + * accessible. In broad terms, a full re-implementation should move the + * directory tree in the local file system and then call + * tree.movedFolder to complete the updating of the workspace + * resource tree to reflect this fact. If unsuccessful in moving the + * directory or any of its descendents in the local file system, + * call tree.failed to report each reason for failure. + * In either case, return true to indicate that the operation + * was attempted. + * The FORCE update flag needs to be honored: unless + * FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the folder subtree is in sync + * before attempting to move it. + * The KEEP_HISTORY update flag needs to be honored as well; use + * tree.addToLocalHistory to capture the contents of any files being + * moved. + *

                  + * A partial re-implementation should perform whatever pre-processing it needs + * to do and then call tree.standardMoveFolder to explicitly + * invoke the standard folder move behavior, which move both the folder + * and its descendents in the local file system and updates the workspace resource + * tree. Return true to indicate that the operation was attempted. + *

                  + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteFolder and returning true. + *

                  + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                  + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param source the handle of the folder to move; the receiver of + * IResource.move(IPath,int,IProgressMonitor) + * @param destination the handle of where the folder will move to; the + * handle equivalent of the first parameter to + * IResource.move(IPath,int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(org.eclipse.core.runtime.IPath,int,IProgressMonitor) + */ + public boolean moveFolder(IResourceTree tree, IFolder source, IFolder destination, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.move(IPath,int,IProgressMonitor) and + * IResource.move(IProjectDescription,int,IProgressMonitor) + * where the receiver is a project. Returns true to accept + * responsibility for implementing this operation as per the API contracts. + *

                  + * On entry to this hook method, the source project is guaranteed to exist + * and be open in the workspace resource tree. If the given description + * contains a different name from that of the given project, then the + * project is being renamed (and its content possibly relocated). If the + * given description contains the same name as the given project, then the + * project is being relocated but not renamed. When the project is being + * renamed, the destination project is guaranteed not to exist in the + * workspace resource tree. + *

                  + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardMoveProject and returning true. + *

                  + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                  + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param source the handle of the open project to move; the receiver of + * IResource.move(IProjectDescription,int,IProgressMonitor) + * or IResource.move(IPath,int,IProgressMonitor) + * @param description the new description of the project; the first + * parameter to + * IResource.move(IProjectDescription,int,IProgressMonitor), or + * a copy of the project's description with the location changed to the + * path given in the first parameter to + * IResource.move(IPath,int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IProjectDescription,int,IProgressMonitor) + * or IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IProjectDescription,int,IProgressMonitor) + * or IResource.move(IPath,int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this method + * attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(org.eclipse.core.runtime.IPath,int,IProgressMonitor) + * @see IResource#move(IProjectDescription,int,IProgressMonitor) + */ + public boolean moveProject(IResourceTree tree, IProject source, IProjectDescription description, int updateFlags, IProgressMonitor monitor); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java new file mode 100644 index 0000000000..5211682459 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java @@ -0,0 +1,383 @@ +/******************************************************************************* + * 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.resources.team; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; + +/** + * Provides internal access to the workspace resource tree for the purposes of + * implementing the move and delete operations. Implementations of + * IMoveDeleteHook call these methods. + * + * @since 2.0 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IResourceTree { + + /** + * Constant indicating that no file timestamp was supplied. + * + * @see #movedFile(IFile, IFile) + */ + public static final long NULL_TIMESTAMP = 0L; + + /** + * Adds the current state of the given file to the local history. + * Does nothing if the file does not exist in the workspace resource tree, + * or if it exists in the workspace resource tree but not in the local file + * system. + *

                  + * This method is used to capture the state of a file in the workspace + * local history before it is overwritten or deleted. + *

                  + * + * @param file the file to be captured + */ + public void addToLocalHistory(IFile file); + + /** + * Returns whether the given resource and its descendents to the given depth + * are considered to be in sync with the local file system. Returns + * false if the given resource does not exist in the workspace + * resource tree, but exists in the local file system; and conversely. + * + * @param resource the resource of interest + * @param depth the depth (one of IResource.DEPTH_ZERO, + * DEPTH_ONE, or DEPTH_INFINITE) + * @return true if the resource is synchronized, and + * false in all other cases + */ + public boolean isSynchronized(IResource resource, int depth); + + /** + * Computes the timestamp for the given file in the local file system. + * Returns NULL_TIMESTAMP if the timestamp of the file in + * the local file system cannot be determined. The file need not exist in + * the workspace resource tree; however, if the file's project does not + * exist in the workspace resource tree, this method returns + * NULL_TIMESTAMP because the project's local content area + * is indeterminate. + *

                  + * Note that the timestamps used for workspace resource tree file + * synchronization are not necessarily interchangeable with + * java.io.File last modification time.The ones computed by + * computeTimestamp may have a higher resolution in some + * operating environments. + *

                  + * + * @param file the file of interest + * @return the local file system timestamp for the file, or + * NULL_TIMESTAMP if it could not be computed + */ + public long computeTimestamp(IFile file); + + /** + * Returns the timestamp for the given file as recorded in the workspace + * resource tree. Returns NULL_TIMESTAMP if the given file + * does not exist in the workspace resource tree, or if the timestamp is + * not known. + *

                  + * Note that the timestamps used for workspace resource tree file + * synchronization are not necessarily interchangeable with + * java.io.File last modification time.The ones computed by + * computeTimestamp may have a higher resolution in some + * operating environments. + *

                  + * + * @param file the file of interest + * @return the workspace resource tree timestamp for the file, or + * NULL_TIMESTAMP if the file does not exist in the + * workspace resource tree, or if the timestamp is not known + */ + public long getTimestamp(IFile file); + + /** + * Updates the timestamp for the given file in the workspace resource tree. + * The file is the local file system is not affected. Does nothing if the + * given file does not exist in the workspace resource tree. + *

                  + * The given timestamp should be that of the corresponding file in the local + * file system (as computed by computeTimestamp). A discrepancy + * between the timestamp of the file in the local file system and the + * timestamp recorded in the workspace resource tree means that the file is + * out of sync (isSynchronized returns false). + *

                  + *

                  + * This operation should be used after movedFile/Folder/Project + * to correct the workspace resource tree record when file timestamps change + * in the course of a move operation. + *

                  + *

                  + * Note that the timestamps used for workspace resource tree file + * synchronization are not necessarily interchangeable with + * java.io.File last modification time.The ones computed by + * computeTimestamp may have a higher resolution in some + * operating environments. + *

                  + * + * @param file the file of interest + * @param timestamp the local file system timestamp for the file, or + * NULL_TIMESTAMP if unknown + * @see #computeTimestamp(IFile) + */ + public void updateMovedFileTimestamp(IFile file, long timestamp); + + /** + * Declares that the operation has failed for the specified reason. + * This method may be called multiple times to report multiple + * failures. All reasons will be accumulated and taken into consideration + * when deciding the outcome of the hooked operation as a whole. + * + * @param reason the reason the operation (or sub-operation) failed + */ + public void failed(IStatus reason); + + /** + * Declares that the given file has been successfully deleted from the + * local file system, and requests that the corresponding deletion should + * now be made to the workspace resource tree. No action is taken if the + * given file does not exist in the workspace resource tree. + *

                  + * This method clears out any markers, session properties, and persistent + * properties associated with the given file. + *

                  + * + * @param file the file that was just deleted from the local file system + */ + public void deletedFile(IFile file); + + /** + * Declares that the given folder and all its descendents have been + * successfully deleted from the local file system, and requests that the + * corresponding deletion should now be made to the workspace resource tree. + * No action is taken if the given folder does not exist in the workspace + * resource tree. + *

                  + * This method clears out any markers, session properties, and persistent + * properties associated with the given folder or its descendents. + *

                  + * + * @param folder the folder that was just deleted from the local file system + */ + public void deletedFolder(IFolder folder); + + /** + * Declares that the given project's content area in the local file system + * has been successfully dealt with in an appropriate manner, and requests + * that the corresponding deletion should now be made to the workspace + * resource tree. No action is taken if the given project does not exist in + * the workspace resource tree. + *

                  + * This method clears out everything associated with this project and any of + * its descendent resources, including: markers; session properties; + * persistent properties; local history; and project-specific plug-ins + * working data areas. The project's content area is not affected. + *

                  + * + * @param project the project being deleted + */ + public void deletedProject(IProject project); + + /** + * Declares that the given source file has been successfully moved to the + * given destination in the local file system, and requests that the + * corresponding changes should now be made to the workspace resource tree. + * No action is taken if the given source file does not exist in the + * workspace resource tree. + *

                  + * The destination file must not already exist in the workspace resource + * tree. + *

                  + * This operation carries over the file timestamp unchanged. Use + * updateMovedFileTimestamp to update the timestamp + * of the file if its timestamp changed as a direct consequence of the move. + *

                  + * + * @param source the handle of the source file that was moved + * @param destination the handle of where the file moved to + * @see #computeTimestamp(IFile) + */ + public void movedFile(IFile source, IFile destination); + + /** + * Declares that the given source folder and its descendents have been + * successfully moved to the given destination in the local file system, + * and requests that the corresponding changes should now be made to the + * workspace resource tree for the folder and all its descendents. No action + * is taken if the given source folder does not exist in the workspace + * resource tree. + *

                  + * This operation carries over file timestamps unchanged. Use + * updateMovedFileTimestamp to update the timestamp of files + * whose timestamps changed as a direct consequence of the move. + *

                  + * The destination folder must not already exist in the workspace resource + * tree. + *

                  + * + * @param source the handle of the source folder that was moved + * @param destination the handle of where the folder moved to + */ + public void movedFolderSubtree(IFolder source, IFolder destination); + + /** + * Declares that the given source project and its files and folders have + * been successfully relocated in the local file system if required, and + * requests that the rename and/or relocation should now be made to the + * workspace resource tree for the project and all its descendents. No + * action is taken if the given project does not exist in the workspace + * resource tree. + *

                  + * This operation carries over file timestamps unchanged. Use + * updateMovedFileTimestamp to update the timestamp of files whose + * timestamps changed as a direct consequence of the move. + *

                  + * If the project is being renamed, the destination project must not + * already exist in the workspace resource tree. + *

                  + * Local history is not preserved if the project is renamed. It is preserved + * when the project's content area is relocated without renaming the + * project. + *

                  + * + * @param source the handle of the source project that was moved + * @param description the new project description + * @return true if the move succeeded, and false + * otherwise + */ + public boolean movedProjectSubtree(IProject source, IProjectDescription description); + + /** + * Deletes the given file in the standard manner from both the local file + * system and from the workspace resource tree. + *

                  + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of file.delete(updateFlags, monitor) because all + * regular API operations that modify resources are off limits. + *

                  + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                  + * + * @param file the file to delete + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + */ + public void standardDeleteFile(IFile file, int updateFlags, IProgressMonitor monitor); + + /** + * Deletes the given folder and its descendents in the standard manner from + * both the local file system and from the workspace resource tree. + *

                  + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of folder.delete(updateFlags, monitor) because all + * regular API operations that modify resources are off limits. + *

                  + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                  + * + * @param folder the folder to delete + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + */ + public void standardDeleteFolder(IFolder folder, int updateFlags, IProgressMonitor monitor); + + /** + * Deletes the given project and its descendents in the standard manner from + * both the local file system and from the workspace resource tree. + *

                  + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of project.delete(updateFlags, monitor) because all + * regular API operations that modify resources are off limits. + *

                  + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                  + * + * @param project the project to delete + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + */ + public void standardDeleteProject(IProject project, int updateFlags, IProgressMonitor monitor); + + /** + * Moves the given file in the standard manner from both the local file + * system and from the workspace resource tree. + *

                  + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of source.move(destination.getProjectRelativePath(), + * updateFlags, monitor) because all regular API operations that + * modify resources are off limits. + *

                  + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                  + * + * @param source the handle of the source file to move + * @param destination the handle of where the file will move to + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + */ + public void standardMoveFile(IFile source, IFile destination, int updateFlags, IProgressMonitor monitor); + + /** + * Moves the given folder and its descendents in the standard manner from + * both the local file system and from the workspace resource tree. + *

                  + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of source.move(destination.getProjectRelativePath(), + * updateFlags, monitor) because all regular API operations that + * modify resources are off limits. + *

                  + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                  + * + * @param source the handle of the source folder to move + * @param destination the handle of where the folder will move to + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + */ + public void standardMoveFolder(IFolder source, IFolder destination, int updateFlags, IProgressMonitor monitor); + + /** + * Renames and/or relocates the given project in the standard manner. + *

                  + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of source.move(description, updateFlags, monitor) + * because all regular API operations that modify resources are off limits. + *

                  + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                  + * + * @param source the handle of the source folder to move + * @param description the new project description + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + */ + public void standardMoveProject(IProject source, IProjectDescription description, int updateFlags, IProgressMonitor monitor); +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java new file mode 100644 index 0000000000..0d0ce91df0 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java @@ -0,0 +1,274 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +package org.eclipse.core.resources.team; + +import java.util.HashSet; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; + +/** + * Default implementation of IResourceRuleFactory. The teamHook extension + * may subclass to provide more specialized scheduling rules for workspace operations that + * they participate in. + * + * @see IResourceRuleFactory + * @since 3.0 + */ +public class ResourceRuleFactory implements IResourceRuleFactory { + private final IWorkspace workspace = ResourcesPlugin.getWorkspace(); + + /** + * Creates a new default resource rule factory. This constructor must only + * be called by subclasses. + */ + protected ResourceRuleFactory() { + super(); + } + + /** + * Default implementation of IResourceRuleFactory#buildRule. + * This default implementation always returns the workspace root. + *

                  + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.resources.IResourceRuleFactory#buildRule() + */ + @Override + public final ISchedulingRule buildRule() { + return workspace.getRoot(); + } + + /** + * Default implementation of IResourceRuleFactory#charsetRule. + * This default implementation always returns the project of the resource + * whose charset setting is being changed, or null if the + * resource is the workspace root. + *

                  + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + *

                  + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#charsetRule(IResource) + * @since 3.1 + */ + @Override + public ISchedulingRule charsetRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return null; + return resource.getProject(); + } + + /** + * Default implementation of IResourceRuleFactory#derivedRule. + * This default implementation always returns null. + *

                  + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#derivedRule(IResource) + * @since 3.6 + */ + @Override + public final ISchedulingRule derivedRule(IResource resource) { + return null; + } + + /** + * Default implementation of IResourceRuleFactory#copyRule. + * This default implementation always returns the parent of the destination + * resource. + *

                  + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#copyRule(IResource, IResource) + */ + @Override + public ISchedulingRule copyRule(IResource source, IResource destination) { + //source is not modified, destination is created + return parent(destination); + } + + /** + * Default implementation of IResourceRuleFactory#createRule. + * This default implementation always returns the parent of the resource + * being created. + *

                  + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#createRule(IResource) + */ + @Override + public ISchedulingRule createRule(IResource resource) { + return parent(resource); + } + + /** + * Default implementation of IResourceRuleFactory#deleteRule. + * This default implementation always returns the parent of the resource + * being deleted. + *

                  + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#deleteRule(IResource) + */ + @Override + public ISchedulingRule deleteRule(IResource resource) { + return parent(resource); + } + + private boolean isReadOnly(IResource resource) { + ResourceAttributes attributes = resource.getResourceAttributes(); + return attributes == null ? false : attributes.isReadOnly(); + } + + /** + * Default implementation of IResourceRuleFactory#markerRule. + * This default implementation always returns null. + *

                  + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.resources.IResourceRuleFactory#markerRule(IResource) + */ + @Override + public final ISchedulingRule markerRule(IResource resource) { + return null; + } + + /** + * Default implementation of IResourceRuleFactory#syncInfoRule. + * This default implementation always returns null. + *

                  + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.resources.IResourceRuleFactory#syncInfoRule(IResource) + */ + @Override + public final ISchedulingRule syncInfoRule(IResource resource) { + return null; + } + + /** + * Default implementation of IResourceRuleFactory#modifyRule. + * This default implementation returns the resource being modified, or the + * parent resource if modifying a project description file. + * Note that this must encompass any rule required by the validateSave hook. + *

                  + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#modifyRule(IResource) + * @see FileModificationValidator#validateSave(IFile) + * @see IProjectDescription#DESCRIPTION_FILE_NAME + */ + @Override + public ISchedulingRule modifyRule(IResource resource) { + IPath path = resource.getFullPath(); + //modifying the project description may cause linked resources to be created or deleted + if (path.segmentCount() == 2 && path.segment(1).equals(IProjectDescription.DESCRIPTION_FILE_NAME)) + return parent(resource); + return resource; + } + + /** + * Default implementation of IResourceRuleFactory#moveRule. + * This default implementation returns a rule that combines the parent + * of the source resource and the parent of the destination resource. + *

                  + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#moveRule(IResource, IResource) + */ + @Override + public ISchedulingRule moveRule(IResource source, IResource destination) { + //move needs the parent of both source and destination + return MultiRule.combine(parent(source), parent(destination)); + } + + /** + * Convenience method to return the parent of the given resource, + * or the resource itself for projects and the workspace root. + * @param resource the resource to compute the parent of + * @return the parent resource for folders and files, and the + * resource itself for projects and the workspace root. + */ + protected final ISchedulingRule parent(IResource resource) { + switch (resource.getType()) { + case IResource.ROOT : + case IResource.PROJECT : + return resource; + default : + return resource.getParent(); + } + } + + /** + * Default implementation of IResourceRuleFactory#refreshRule. + * This default implementation always returns the parent of the resource + * being refreshed. + *

                  + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#refreshRule(IResource) + */ + @Override + public ISchedulingRule refreshRule(IResource resource) { + return parent(resource); + } + + /** + * Default implementation of IResourceRuleFactory#validateEditRule. + * This default implementation returns a rule that combines the parents of + * all read-only resources, or null if there are no read-only + * resources. + *

                  + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#validateEditRule(IResource[]) + */ + @Override + public ISchedulingRule validateEditRule(IResource[] resources) { + if (resources.length == 0) + return null; + //optimize rule for single file + if (resources.length == 1) + return isReadOnly(resources[0]) ? parent(resources[0]) : null; + //need a lock on the parents of all read-only files + HashSet rules = new HashSet<>(); + for (int i = 0; i < resources.length; i++) + if (isReadOnly(resources[i])) + rules.add(parent(resources[i])); + if (rules.isEmpty()) + return null; + if (rules.size() == 1) + return rules.iterator().next(); + ISchedulingRule[] ruleArray = rules.toArray(new ISchedulingRule[rules.size()]); + return new MultiRule(ruleArray); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java new file mode 100644 index 0000000000..4e605a1ce2 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java @@ -0,0 +1,231 @@ +/******************************************************************************* + * 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.team; + +import java.net.URI; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.resources.InternalTeamHook; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * A general hook class for operations that team providers may be + * interested in participating in. Implementors of the hook should provide + * a concrete subclass, and override any methods they are interested in. + *

                  + * This class is intended to be subclassed by the team component in + * conjunction with the org.eclipse.core.resources.teamHook + * standard extension point. Individual team providers may also subclass this + * class. It is not intended to be subclassed by other clients. The methods + * defined on this class are called from within the implementations of + * workspace API methods and must not be invoked directly by clients. + *

                  + * + * @since 2.1 + */ +public abstract class TeamHook extends InternalTeamHook { + /** + * The default resource scheduling rule factory. This factory can be used for projects + * that the team hook methods do not participate in. + * + * @see #getRuleFactory(IProject) + * @see #setRuleFactory(IProject, IResourceRuleFactory) + * @since 3.0 + */ + protected final IResourceRuleFactory defaultFactory = new ResourceRuleFactory(); + + /** + * Creates a new team hook. Default constructor for use by subclasses and the + * resources plug-in only. + */ + protected TeamHook() { + super(); + } + + /** + * Returns the resource scheduling rule factory that should be used when workspace + * operations are invoked on resources in that project. The workspace will ask the + * team hook this question only once per project, per session. The workspace will + * assume the returned result is valid for the rest of that session, unless the rule + * is changed by calling setRuleFactory. + *

                  + * This method must not return null. If no special rules are required + * by the team hook for the given project, the value of the defaultFactory + * field should be returned. + *

                  + * This default implementation always returns the value of the defaultFactory + * field. Subclasses may override and provide a subclass of ResourceRuleFactory. + * + * @param project the project to return scheduling rules for + * @return the resource scheduling rules for a project + * @see #setRuleFactory(IProject, IResourceRuleFactory) + * @see ResourceRuleFactory + * @since 3.0 + */ + public IResourceRuleFactory getRuleFactory(IProject project) { + return defaultFactory; + } + + /** + * Sets the resource scheduling rule factory to use for resource modifications + * in the given project. This method only needs to be called if the factory has changed + * since the initial call to getRuleFactory for the given project + *

                  + * The supplied factory must not be null. If no special rules are required + * by the team hook for the given project, the value of the defaultFactory + * field should be used. + *

                  + * Note that the new rule factory will only take effect for resource changing + * operations that begin after this method completes. Care should be taken to + * avoid calling this method during the invocation of any resource changing + * operation (in any thread). The best time to change rule factories is during resource + * change notification when the workspace is locked for modification. + * + * @param project the project to change the resource rule factory for + * @param factory the new resource rule factory + * @see #getRuleFactory(IProject) + * @see IResourceRuleFactory + * @since 3.0 + */ + @Override + protected final void setRuleFactory(IProject project, IResourceRuleFactory factory) { + super.setRuleFactory(project, factory); + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of IFile.createLink. + *

                  + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                  + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

                  + * + * @param file the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system path where the file should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + */ + public IStatus validateCreateLink(IFile file, int updateFlags, IPath location) { + return Status.OK_STATUS; + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of {@link IFile#createLink(URI, int, IProgressMonitor) } + *

                  + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                  + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

                  + * + * @param file the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system URI where the file should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + * @since 3.2 + */ + public IStatus validateCreateLink(IFile file, int updateFlags, URI location) { + //forward to old method to ensure old hooks get a chance to validate in the local case + if (EFS.SCHEME_FILE.equals(location.getScheme())) + return validateCreateLink(file, updateFlags, URIUtil.toPath(location)); + return Status.OK_STATUS; + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of IFolder.createLink. + *

                  + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                  + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

                  + * + * @param folder the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system path where the folder should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + */ + public IStatus validateCreateLink(IFolder folder, int updateFlags, IPath location) { + return Status.OK_STATUS; + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of {@link IFolder#createLink(URI, int, IProgressMonitor)} + *

                  + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                  + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

                  + * + * @param folder the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system path where the folder should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + * @since 3.2 + */ + public IStatus validateCreateLink(IFolder folder, int updateFlags, URI location) { + //forward to old method to ensure old hooks get a chance to validate in the local case + if (EFS.SCHEME_FILE.equals(location.getScheme())) + return validateCreateLink(folder, updateFlags, URIUtil.toPath(location)); + return Status.OK_STATUS; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html new file mode 100644 index 0000000000..5069b75ab0 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html @@ -0,0 +1,19 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the Team component. + +

                  Package Specification

                  +This package specifies the APIs in the Resources plug-in which are intended +to be implemented by the Team component. This provides hooks into the +internal workspace tree mechanism as well as lets Team providers receive pre-notification +for resource API calls such as move and delete. + + + diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java new file mode 100644 index 0000000000..7f30a04808 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.resources.variableresolvers; + +import org.eclipse.core.resources.IResource; + +/** + * An interface that variable providers should implement in order + * to extends the default path variable list used to resolve relative + * locations of linked resources. + * @since 3.6 + */ +public abstract class PathVariableResolver { + + /** + * This method can return a list of possible variables resolved by + * this resolver. + *

                  + * This default implementation always returns null. Subclasses + * should override to provide custom extensions. + *

                  + * + * @param variable + * The current variable name. + * @param resource + * The resource that the variable is being resolved for. + * @return the list of supported variables + */ + public String[] getVariableNames(String variable, IResource resource) { + return null; + } + + /** + * Returns a variable value + * + * @param variable + * The current variable name. + * @param resource + * The resource that the variable is being resolved for. + * @return the variable value. + */ + public abstract String getValue(String variable, IResource resource); +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html new file mode 100644 index 0000000000..6a477adf0e --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html @@ -0,0 +1,21 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the path variable providers. + +

                  Package Specification

                  +

                  +This package specifies the APIs in the Resources plug-in that are intended +to be implemented and used by plug-ins using the variableResolvers extension point. +

                  +@since 3.6 +

                  + + + diff --git a/third_party/patches/neon/pom.xml b/third_party/patches/neon/pom.xml new file mode 100644 index 0000000000..1e09544d8c --- /dev/null +++ b/third_party/patches/neon/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + com.google.cloud.tools.eclipse + trunk + 0.1.0-SNAPSHOT + ../.. + + eclipse-patches-neon + 0.1.0-SNAPSHOT + pom + + Patches bundles for Eclipse Neon + + + org.eclipse.core.resources + + diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/.classpath b/third_party/patches/oxygen/org.eclipse.core.resources/.classpath new file mode 100644 index 0000000000..1582404385 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/.project b/third_party/patches/oxygen/org.eclipse.core.resources/.project new file mode 100644 index 0000000000..5b64045589 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/.project @@ -0,0 +1,17 @@ + + + org.eclipse.core.resources + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/META-INF/MANIFEST.MF b/third_party/patches/oxygen/org.eclipse.core.resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..1c141935b9 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/META-INF/MANIFEST.MF @@ -0,0 +1,32 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true +Bundle-Version: 3.12.0.v20170417-1558 +Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin +Bundle-Vendor: %providerName +Bundle-Localization: plugin +Export-Package: org.eclipse.core.internal.dtree;x-internal:=true, + org.eclipse.core.internal.events;x-internal:=true, + org.eclipse.core.internal.localstore;x-internal:=true, + org.eclipse.core.internal.properties;x-internal:=true, + org.eclipse.core.internal.propertytester;x-internal:=true, + org.eclipse.core.internal.refresh;x-internal:=true, + org.eclipse.core.internal.resources;x-internal:=true, + org.eclipse.core.internal.resources.mapping;x-internal:=true, + org.eclipse.core.internal.resources.projectvariables;x-internal:=true, + org.eclipse.core.internal.resources.refresh.win32;x-internal:=true, + org.eclipse.core.internal.utils;x-internal:=true, + org.eclipse.core.internal.watson;x-internal:=true, + org.eclipse.core.resources, + org.eclipse.core.resources.filtermatchers, + org.eclipse.core.resources.mapping, + org.eclipse.core.resources.refresh, + org.eclipse.core.resources.team, + org.eclipse.core.resources.variableresolvers +Require-Bundle: org.eclipse.ant.core;bundle-version="[3.1.0,4.0.0)";resolution:=optional, + org.eclipse.core.expressions;bundle-version="[3.2.0,4.0.0)", + org.eclipse.core.filesystem;bundle-version="[1.3.0,2.0.0)", + org.eclipse.core.runtime;bundle-version="[3.12.0,4.0.0)" +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties b/third_party/patches/oxygen/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties new file mode 100644 index 0000000000..427a017763 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/OSGI-INF/l10n/bundle-src.properties @@ -0,0 +1,4 @@ +#Source Bundle Localization +#Tue Jul 18 04:25:37 EDT 2017 +bundleVendor=Eclipse.org +bundleName=Core Resource Management Source diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/about.html b/third_party/patches/oxygen/org.eclipse.core.resources/about.html new file mode 100644 index 0000000000..460233046e --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/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/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/META-INF/eclipse.inf b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/ConvertPath.java b/third_party/patches/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/ConvertPath.java new file mode 100644 index 0000000000..8b55fea240 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/IncrementalBuild.java b/third_party/patches/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/IncrementalBuild.java new file mode 100644 index 0000000000..f30025867d --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/Policy.java b/third_party/patches/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/Policy.java new file mode 100644 index 0000000000..de2c8da95f --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/RefreshLocalTask.java b/third_party/patches/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/RefreshLocalTask.java new file mode 100644 index 0000000000..19f2d7b963 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/ant_tasks/resources-antsrc/org/eclipse/core/resources/ant/messages.properties b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/build.properties b/third_party/patches/oxygen/org.eclipse.core.resources/build.properties new file mode 100644 index 0000000000..f646e2c10a --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/natives/make.bat b/third_party/patches/oxygen/org.eclipse.core.resources/natives/make.bat new file mode 100644 index 0000000000..615ba2027b --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/natives/make.bat @@ -0,0 +1,29 @@ +@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 +"C:\Program Files\Java\jdk1.8.0_65\bin\javah.exe" org.eclipse.core.internal.resources.refresh.win32.Win32Natives +move org_eclipse_core_internal_resources_refresh_win32_Win32Natives.h ..\natives\ref2.h + +REM compile and link +cd ..\natives +set win_include="C:\Program Files\Microsoft Visual Studio 14.0\VC\include" +set jdk_include="C:\Program Files\Java\jdk1.8.0_65\include" + +set dll_name=win32refresh.dll + +call "c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64_x86 +"cl.exe" -I%win_include% -I%jdk_include% -I%jdk_include%\win32 -LD ref.c -Fe%dll_name% +move %dll_name% ..\..\org.eclipse.core.resources.win32.x86\os\win32\x86\%dll_name% + +call "c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +"cl.exe" -I%win_include% -I%jdk_include% -I%jdk_include%\win32 -LD ref.c -Fe%dll_name% +move %dll_name% ..\..\org.eclipse.core.resources.win32.x86_64\os\win32\x86_64\%dll_name% \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/natives/readme.txt b/third_party/patches/oxygen/org.eclipse.core.resources/natives/readme.txt new file mode 100644 index 0000000000..e27ecd6c00 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/natives/ref.c b/third_party/patches/oxygen/org.eclipse.core.resources/natives/ref.c new file mode 100644 index 0000000000..d716a57155 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/natives/ref.c @@ -0,0 +1,308 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + * Mikael Barbero (Eclipse Foundation) - 286681 handle WAIT_ABANDONED_0 return value + *******************************************************************************/ +#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_ABANDONED_0 + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_WAIT_1ABANDONED_10 +(JNIEnv *env, jclass this) { + return WAIT_ABANDONED_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/oxygen/org.eclipse.core.resources/natives/ref.h b/third_party/patches/oxygen/org.eclipse.core.resources/natives/ref.h new file mode 100644 index 0000000000..2883e8dd18 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/natives/ref.h @@ -0,0 +1,221 @@ +/* + * 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 + */ +/* 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_ABANDONED_0 + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_eclipse_core_internal_resources_refresh_win32_Win32Natives_WAIT_1ABANDONED_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/oxygen/org.eclipse.core.resources/plugin.properties b/third_party/patches/oxygen/org.eclipse.core.resources/plugin.properties new file mode 100644 index 0000000000..1c45ea381d --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/plugin.xml b/third_party/patches/oxygen/org.eclipse.core.resources/plugin.xml new file mode 100644 index 0000000000..7f8663ce31 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/plugin.xml @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/schema/builders.exsd b/third_party/patches/oxygen/org.eclipse.core.resources/schema/builders.exsd new file mode 100644 index 0000000000..03a7a180fb --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/schema/builders.exsd @@ -0,0 +1,234 @@ + + + + + + + + + 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 + + + + + + + + + + + + The fully-qualified name of a class that implements <samp>org.eclipse.core.resources.IDynamicReferenceProvider</samp>. + If supplied, the given class will asked to return a list of other projects that + must be built before this one when the build system is computing the build order. + + + + + + + + + + + + + + + + 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/oxygen/org.eclipse.core.resources/schema/fileModificationValidator.exsd b/third_party/patches/oxygen/org.eclipse.core.resources/schema/fileModificationValidator.exsd new file mode 100644 index 0000000000..2336209991 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/schema/filterMatchers.exsd b/third_party/patches/oxygen/org.eclipse.core.resources/schema/filterMatchers.exsd new file mode 100644 index 0000000000..5bcd92a610 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/schema/markers.exsd b/third_party/patches/oxygen/org.eclipse.core.resources/schema/markers.exsd new file mode 100644 index 0000000000..a263442c10 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/schema/modelProviders.exsd b/third_party/patches/oxygen/org.eclipse.core.resources/schema/modelProviders.exsd new file mode 100644 index 0000000000..aa9b999c97 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/schema/moveDeleteHook.exsd b/third_party/patches/oxygen/org.eclipse.core.resources/schema/moveDeleteHook.exsd new file mode 100644 index 0000000000..46cf633f16 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/schema/natures.exsd b/third_party/patches/oxygen/org.eclipse.core.resources/schema/natures.exsd new file mode 100644 index 0000000000..42cf2493d8 --- /dev/null +++ b/third_party/patches/oxygen/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 &quot;false&quot;, 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/oxygen/org.eclipse.core.resources/schema/refreshProviders.exsd b/third_party/patches/oxygen/org.eclipse.core.resources/schema/refreshProviders.exsd new file mode 100644 index 0000000000..b34d044b74 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/schema/teamHook.exsd b/third_party/patches/oxygen/org.eclipse.core.resources/schema/teamHook.exsd new file mode 100644 index 0000000000..60dc9762c5 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/schema/variableResolvers.exsd b/third_party/patches/oxygen/org.eclipse.core.resources/schema/variableResolvers.exsd new file mode 100644 index 0000000000..b32631b164 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTree.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTree.java new file mode 100644 index 0000000000..f5ef7084c7 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java new file mode 100644 index 0000000000..ad395a0f50 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java new file mode 100644 index 0000000000..3acee2c63c --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java new file mode 100644 index 0000000000..d8f7802e08 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeLookup.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java new file mode 100644 index 0000000000..9a7917b6c0 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java @@ -0,0 +1,366 @@ +/******************************************************************************* + * 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); + } + + @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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java new file mode 100644 index 0000000000..502a5c716f --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java new file mode 100644 index 0000000000..7f08ebf5e2 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java new file mode 100644 index 0000000000..9dda6ed19b --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java new file mode 100644 index 0000000000..35fb66d206 --- /dev/null +++ b/third_party/patches/oxygen/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)); + } + + /** + * @see org.eclipse.core.internal.utils.IStringPoolParticipant#shareStrings(StringPool) + */ + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java new file mode 100644 index 0000000000..f04ce018ab --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java new file mode 100644 index 0000000000..d5012bd871 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java new file mode 100644 index 0000000000..055f9318ff --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NodeComparison.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NodeComparison.java new file mode 100644 index 0000000000..4cf9794f75 --- /dev/null +++ b/third_party/patches/oxygen/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() { + StringBuilder buf = new StringBuilder("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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java new file mode 100644 index 0000000000..9d21fbf8e8 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/TestHelper.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java new file mode 100644 index 0000000000..dfb378d521 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java @@ -0,0 +1,273 @@ +/******************************************************************************* + * 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 volatile 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 { + SubMonitor subMonitor = SubMonitor.convert(monitor, Policy.opWork + 1); + final ISchedulingRule rule = workspace.getRuleFactory().buildRule(); + try { + workspace.prepareOperation(rule, subMonitor.split(1)); + 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, subMonitor.split(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); + } + } + + /** + * 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) { + SubMonitor subMonitor = SubMonitor.convert(monitor, 1); + synchronized (this) { + if (subMonitor.isCanceled()) { + return canceled(); + } + } + //if the system is shutting down, don't build + if (systemBundle.getState() == Bundle.STOPPING) + return Status.OK_STATUS; + try { + doBuild(subMonitor.split(1)); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java new file mode 100644 index 0000000000..6f2f396ae2 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java @@ -0,0 +1,291 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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())); + } + } + } + + @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); + } + + @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; + } + + @Override + public String getBuilderName() { + return getName(); + } + + @Override + public int hashCode() { + // hash on name and trigger + return 37 * getName().hashCode() + (ALL_TRIGGERS & triggers); + } + + @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; + } + + @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; + } + + @Override + public void setBuilderName(String value) { + //don't allow builder name to be null + setName(value == null ? "" : value); //$NON-NLS-1$ + } + + @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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java new file mode 100644 index 0000000000..ddc60ad9ea --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildContext.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * 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(); + } + + @Override + public IBuildConfiguration[] getAllReferencedBuildConfigs() { + int position = findBuildConfigurationIndex(); + IBuildConfiguration[] builtBefore = new IBuildConfiguration[position]; + System.arraycopy(buildOrder, 0, builtBefore, 0, builtBefore.length); + return builtBefore; + } + + @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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java new file mode 100644 index 0000000000..6da88ae72b --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java @@ -0,0 +1,1158 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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(); + StringBuilder sb = new StringBuilder("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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java new file mode 100644 index 0000000000..3622dfcef2 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ILifecycleListener.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java new file mode 100644 index 0000000000..fa977efbf9 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/LifecycleEvent.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NodeIDMap.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java new file mode 100644 index 0000000000..af798114da --- /dev/null +++ b/third_party/patches/oxygen/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 ICoreRunnable noop = new ICoreRunnable() { + @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 volatile 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java new file mode 100644 index 0000000000..6ebca79035 --- /dev/null +++ b/third_party/patches/oxygen/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$ + StringBuilder sb = new StringBuilder(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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java new file mode 100644 index 0000000000..6632b54ac2 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java new file mode 100644 index 0000000000..a3b5e4e93a --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java new file mode 100644 index 0000000000..3fe689e1d7 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java new file mode 100644 index 0000000000..f2f4b44d0a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java @@ -0,0 +1,552 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Mickael Istria (Red Hat Inc.) - Bug 488938 + *******************************************************************************/ +package org.eclipse.core.internal.events; + +import java.util.Map; +import java.util.Map.Entry; +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 StringBuilder buffer = new StringBuilder(); + writeDebugString(buffer); + return buffer.toString(); + } + + /** + * Returns a string representation of this delta's + * deep structure suitable for debug purposes. + */ + public String toDeepDebugString() { + final StringBuilder buffer = new StringBuilder("\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(StringBuilder 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(StringBuilder buffer) { + Map markerDeltas = deltaInfo.getMarkerDeltas(); + if (markerDeltas == null || markerDeltas.isEmpty()) + return; + buffer.append('['); + for (Entry entry : markerDeltas.entrySet()) { + IPath key = entry.getKey(); + if (getResource().getFullPath().equals(key)) { + MarkerSet set = entry.getValue(); + IMarkerSetElement[] deltas = set.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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java new file mode 100644 index 0000000000..58b1c2d244 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaInfo.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java new file mode 100644 index 0000000000..22562d59a2 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java new file mode 100644 index 0000000000..7a9d16bb2d --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java @@ -0,0 +1,138 @@ +/******************************************************************************* + * 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; + } + + /** + * @see UniversalUniqueIdentifier#appendByteString(StringBuilder, byte) + */ + @SuppressWarnings("javadoc") + private void appendByteString(StringBuilder 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); + } + + /** + * Converts an array of bytes into a String. + * + * @see UniversalUniqueIdentifier#toString() + */ + private String bytesToHexString(byte[] b) { + StringBuilder buffer = new StringBuilder(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java new file mode 100644 index 0000000000..82bd8bb59b --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java @@ -0,0 +1,396 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java new file mode 100644 index 0000000000..6fdf857a1b --- /dev/null +++ b/third_party/patches/oxygen/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(); + StringBuilder locationBuffer = new StringBuilder(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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java new file mode 100644 index 0000000000..967340d7ce --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java new file mode 100644 index 0000000000..8e5f36d315 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java new file mode 100644 index 0000000000..13d9bb8e7f --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * 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); + // For a linked resource itself we have to use its root, but for its children we prefer + // to use the canonical root since it provides for faster file system access. + // See http://bugs.eclipse.org/507084 + final URI uri = resource.getPathVariableManager().resolveURI(resource.isLinked() ? root : 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); + } + IFileStore 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java new file mode 100644 index 0000000000..3c3cc748fe --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java @@ -0,0 +1,1236 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * 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 + * - [462440] IFile#getContents methods should specify the status codes for its exceptions + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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.refresh.RefreshManager; +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) { + RefreshManager refreshManager = workspace.getRefreshManager(); + // refreshManager can be null during shutdown + if (refreshManager != null) { + refreshManager.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. + *

                  + *

                  + * Returns a folder whose path has a minimal number of segments. + * I.e. a folder in a nested project is preferred over a folder in an enclosing project. + *

                  + */ + public IContainer containerForLocation(IPath location) { + return (IContainer) resourceForLocation(location, false); + } + + /** + * Returns a 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. + *

                  + * Returns a resource whose path has a minimal number of segments. + * I.e. a resource in a nested project is preferred over a resource in an enclosing project. + *

                  + */ + private IResource resourceForLocation(IPath location, boolean files) { + if (workspace.getRoot().getLocation().equals(location)) { + if (!files) + return resourceFor(Path.ROOT, false); + return null; + } + int resultProjectPathSegments = 0; + IResource result = 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(); + if (segmentsToRemove > resultProjectPathSegments) { + IPath path = project.getFullPath().append(location.removeFirstSegments(segmentsToRemove)); + IResource resource = resourceFor(path, files); + if (resource != null && !((Resource) resource).isFiltered()) { + resultProjectPathSegments = segmentsToRemove; + result = resource; + } + } + } + } + return result; + } + + public void copy(IResource target, IResource destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + String title = NLS.bind(Messages.localstore_copying, target.getFullPath()); + + SubMonitor subMonitor = SubMonitor.convert(monitor, title, 100); + 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, subMonitor.split(100)); + UnifiedTree tree = new UnifiedTree(target); + tree.accept(visitor, IResource.DEPTH_INFINITE); + IStatus status = visitor.getStatus(); + if (!status.isOK()) { + throw new ResourceException(status); + } + } + + public void delete(IResource target, int flags, IProgressMonitor monitor) throws CoreException { + + 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()); + + SubMonitor subMonitor = SubMonitor.convert(monitor, title, deleteWork + refreshWork); + 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) { + CollectSyncStatusVisitor refreshVisitor = new CollectSyncStatusVisitor(Messages.localstore_deleteProblem, subMonitor.split(refreshWork)); + refreshVisitor.setIgnoreLocalDeletions(true); + tree.accept(refreshVisitor, IResource.DEPTH_INFINITE); + status.merge(refreshVisitor.getSyncStatus()); + skipList = refreshVisitor.getAffectedResources(); + } + DeleteVisitor deleteVisitor = new DeleteVisitor(skipList, flags, subMonitor.split(deleteWork), deleteWork); + tree.accept(deleteVisitor, IResource.DEPTH_INFINITE); + status.merge(deleteVisitor.getStatus()); + if (!status.isOK()) { + throw new ResourceException(status); + } + + } + + /** + * 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. + *

                  + *

                  + * Returns a file whose path has a minimal number of segments. + * I.e. a file in a nested project is preferred over a file in an enclosing project. + *

                  + */ + 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, SubMonitor.convert(null)); + workspace.getAliasManager().updateAliases(descriptionFile, getStore(descriptionFile), IResource.DEPTH_ZERO, SubMonitor.convert(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(SubMonitor.convert(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); + String message = NLS.bind(Messages.localstore_fileNotFound, store.toString()); + throw new ResourceException(IResourceStatus.RESOURCE_NOT_FOUND, 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 { + + //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, SubMonitor.convert(null))); + // IFileStore#openInputStream may cancel the monitor, thus the monitor state is checked + 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 { + String title = NLS.bind(Messages.localstore_refreshing, target.getFullPath()); + SubMonitor subMonitor = SubMonitor.convert(monitor, title, 100); + IFileTree fileTree = null; + // If there can be more than one resource to refresh, try to get the whole tree in one shot, if the file system supports it. + if (depth != IResource.DEPTH_ZERO) { + IFileStore fileStore = ((Resource) target).getStore(); + fileTree = fileStore.getFileSystem().fetchFileTree(fileStore, subMonitor.newChild(2)); + } + UnifiedTree tree = fileTree == null ? new UnifiedTree(target) : new UnifiedTree(target, fileTree); + SubMonitor refreshMonitor = subMonitor.newChild(98); + RefreshLocalVisitor visitor = updateAliases ? new RefreshLocalAliasVisitor(refreshMonitor) : new RefreshLocalVisitor(refreshMonitor); + tree.accept(visitor, depth); + IStatus result = visitor.getErrorStatus(); + if (!result.isOK()) + throw new ResourceException(result); + return visitor.resourcesChanged(); + } + + /** + * 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 { + IProject[] projects = target.getProjects(IContainer.INCLUDE_HIDDEN); + String title = Messages.localstore_refreshingRoot; + SubMonitor subMonitor = SubMonitor.convert(monitor, title, projects.length); + // 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, subMonitor.newChild(1)); + } + return changed; + } + + /** + * 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 { + SubMonitor subMonitor = SubMonitor.convert(monitor, 4); + 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); + + // On Windows an attempt to open an output stream on a hidden file results in FileNotFoundException. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=194216 + boolean restoreHiddenAttribute = false; + if (fileInfo.exists() && fileInfo.getAttribute(EFS.ATTRIBUTE_HIDDEN) && Platform.getOS().equals(Platform.OS_WIN32)) { + fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, false); + store.putInfo(fileInfo, EFS.SET_ATTRIBUTES, subMonitor.split(1)); + restoreHiddenAttribute = true; + } else { + subMonitor.split(1); + } + int options = append ? EFS.APPEND : EFS.NONE; + OutputStream out = store.openOutputStream(options, subMonitor.split(1)); + if (restoreHiddenAttribute) { + fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, true); + store.putInfo(fileInfo, EFS.SET_ATTRIBUTES, subMonitor.split(1)); + } else { + subMonitor.split(1); + } + FileUtil.transferStreams(content, out, store.toString(), subMonitor.split(1)); + // 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java new file mode 100644 index 0000000000..46947329c7 --- /dev/null +++ b/third_party/patches/oxygen/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. + *

                  + * Version 2 (3.1 M5): + *

                  +	 * FILE ::= VERSION_ID ENTRY+
                  +	 * ENTRY ::= PATH STATE_COUNT STATE+
                  +	 * PATH ::= string (does not include project name)
                  +	 * STATE_COUNT ::= int
                  +	 * STATE ::= UUID LAST_MODIFIED
                  +	 * UUID	 ::= byte[16]
                  +	 * LAST_MODIFIED ::= byte[8]
                  +	 * 
                  + *

                  + *

                  + * Version 1 (3.1 M4): + *

                  +	 * FILE ::= VERSION_ID ENTRY+
                  +	 * ENTRY ::= PATH STATE_COUNT STATE+
                  +	 * PATH ::= string
                  +	 * STATE_COUNT ::= int
                  +	 * STATE ::= UUID LAST_MODIFIED
                  +	 * UUID	 ::= byte[16]
                  +	 * LAST_MODIFIED ::= byte[8]
                  +	 * 
                  + *

                  + */ + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java new file mode 100644 index 0000000000..96f49382b0 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryStore2.java @@ -0,0 +1,383 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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<>(); + } + } + + @Override + public void closeHistoryStore(IResource resource) { + try { + tree.getCurrent().save(); + tree.getCurrent().flush(); + } catch (CoreException e) { + log(e); + } + } + + @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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java new file mode 100644 index 0000000000..68e89b532a --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/ILocalStoreConstants.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IUnifiedTreeVisitor.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java new file mode 100644 index 0000000000..6c59391511 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java new file mode 100644 index 0000000000..3312efaf23 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java new file mode 100644 index 0000000000..ff482f2d5f --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java new file mode 100644 index 0000000000..7beab2c064 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.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 + * Sergey Prigogin (Google) - [482064] Incorrect SubMonitor usage in RefreshLocalVisitor.visit + *******************************************************************************/ +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; + + // Progress monitor will initially move by 1. / TOTAL_WORK per resource but will gradually slow down + // as more resources are discovered. + public static final int TOTAL_WORK = 1000; + + protected MultiStatus errors; + protected SubMonitor monitor; + protected boolean resourceChanged; + protected Workspace workspace; + + public RefreshLocalVisitor(IProgressMonitor monitor) { + this.monitor = SubMonitor.convert(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 { + // The monitor will asymptotically approach 100% as the number of processed resources increases. + monitor.setWorkRemaining(TOTAL_WORK).worked(1); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java new file mode 100644 index 0000000000..f9fcb9d8e4 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyOutputStream.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java new file mode 100644 index 0000000000..8ed7fa5a70 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java @@ -0,0 +1,575 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + * Sergey Prigogin (Google) - 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 = {}; + + /** Singleton to indicate no local children */ + private static final IResource[] NO_RESOURCES = {}; + + /** + * True if the level of the children of the current node are valid according + * to the requested refresh depth, false otherwise + */ + protected boolean childLevelValid; + + /** an IFileTree which can be used to build a unified tree*/ + protected IFileTree fileTree; + + /** 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.toPath().toRealPath().toString() + java.io.File.separatorChar; + String childPath = childFile.toPath().toRealPath().toString() + java.io.File.separatorChar; + //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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java new file mode 100644 index 0000000000..a296c2fafc --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java new file mode 100644 index 0000000000..e27e59eba5 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java new file mode 100644 index 0000000000..762e74e121 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyBucket.java @@ -0,0 +1,350 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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. + *

                  + * Version 1: + *

                  +	 * FILE ::= VERSION_ID ENTRY+
                  +	 * ENTRY ::= PATH PROPERTY_COUNT PROPERTY+
                  +	 * PATH ::= string (does not contain project name)
                  +	 * PROPERTY_COUNT ::= int
                  +	 * PROPERTY ::= QUALIFIER LOCAL_NAME VALUE
                  +	 * QUALIFIER ::= INDEX | QNAME
                  +	 * INDEX -> byte int
                  +	 * QNAME -> byte string
                  +	 * UUID ::= byte[16]
                  +	 * LAST_MODIFIED ::= byte[8]
                  +	 * 
                  + *

                  + */ + 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); + } + + @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; + } + + @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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java new file mode 100644 index 0000000000..12323ed26e --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java @@ -0,0 +1,192 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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(), Integer.toString(MAX_VALUE_SIZE)}); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java new file mode 100644 index 0000000000..56695af0a7 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * 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$ + + @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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java new file mode 100644 index 0000000000..14aae60ea2 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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$ + + @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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java new file mode 100644 index 0000000000..4dad57525e --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java new file mode 100644 index 0000000000..d618e56b46 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java new file mode 100644 index 0000000000..8eae891b81 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java @@ -0,0 +1,231 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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; + StringBuilder buf = new StringBuilder(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java new file mode 100644 index 0000000000..ea66d26940 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * 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; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; + +/** + * 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) { + Workspace workspace = (Workspace) resource.getWorkspace(); + RefreshManager refreshManager = workspace.getRefreshManager(); + MonitorManager monitors = refreshManager.monitors; + PollingMonitor pollingMonitor = monitors.pollMonitor; + pollingMonitor.monitor(resource); + return pollingMonitor; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.refresh.RefreshProvider#resetMonitors(IResource) + */ + public void resetMonitors(IResource resource, IProgressMonitor progressMonitor) { + SubMonitor subMonitor = SubMonitor.convert(progressMonitor, 2); + MonitorManager manager = ((Workspace) resource.getWorkspace()).getRefreshManager().monitors; + manager.unmonitor(resource, subMonitor.split(1)); + manager.monitor(resource, subMonitor.split(1)); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorJob.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorJob.java new file mode 100644 index 0000000000..d0193aac0e --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorJob.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2016 Eclipse Foundation 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: + * Mikael Barbero (Eclipse Foundation) - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.refresh; + +import java.util.Collection; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.*; + +/** + * A specific kind of system job used for installing and uninstalling auto-refresh monitors on resources. + *

                  + * MonitorJobs have specific scheduling rules. These rules only apply to instances of MonitorJob. The + * rules conflicts with and contains the same rules as the resource(s) given to the factory methods + * for creating the MonitorJob. + *

                  + * MonitorJob belongs to the {@link ResourcesPlugin#FAMILY_AUTO_REFRESH auto refresh family} + * for testing purpose. Joining on jobs of this family is sometime mandatory for testing conditions + * after changing some resources. + */ +public class MonitorJob extends Job { + private final ICoreRunnable runnable; + + private MonitorJob(String name, MonitorRule rule, ICoreRunnable runnable) { + super(name); + this.runnable = runnable; + setSystem(true); + setRule(rule); + } + + /** + * Returns a new monitor job with the given name, resource on which the scheduling rule will + * be built and core runnable. + * + * @param name the name of the returned job + * @param resource the resource on which the scheduling rule of the returned job will be built. + * @param runnable the core runnable that will be executed by the returned job + */ + static Job createSystem(final String name, IResource resource, final ICoreRunnable runnable) { + return new MonitorJob(name, MonitorRule.create(resource), runnable); + } + + /** + * Returns a new monitor job with the given name, resources on which the scheduling rule will + * be built and core runnable. + * + * @param name the name of the returned job + * @param resources the resources on which the scheduling rule of the returned job will be built. + * @param runnable the core runnable that will be executed by the returned job + */ + static Job createSystem(final String name, Collection resources, final ICoreRunnable runnable) { + return new MonitorJob(name, MonitorRule.create(resources), runnable); + } + + /** + * A specific scheduling rule for {@link MonitorJob}. + *

                  + * It conflicts with and contains other instances of this kind of scheduling + * rule which have been created with conflicting and containing {@link IResource}s. + */ + private static class MonitorRule implements ISchedulingRule { + private static final ISchedulingRule[] SCHEDULING_RULE__EMPTY_ARR = new ISchedulingRule[0]; + + private final ISchedulingRule resourceRule; + + MonitorRule(ISchedulingRule schedulingRule) { + resourceRule = schedulingRule; + } + + /** + * Create a new {@link MonitorRule} that will be conflicting with + * the same rules and will contain the same rules as the given resource. + * + * @param resource the resource to wrap + * @return a new {@link MonitorRule}. + */ + static MonitorRule create(IResource resource) { + return new MonitorRule(resource); + } + + /** + * Create a new {@link MonitorRule} that will be conflicting with + * the same rules and will contain the same rules as the given resources. + * + * @param resources the resources to wrap + * @return a new {@link MonitorRule}. + */ + static MonitorRule create(Collection resources) { + return new MonitorRule(MultiRule.combine(resources.toArray(SCHEDULING_RULE__EMPTY_ARR))); + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + if (rule instanceof MonitorRule) { + return resourceRule.isConflicting(((MonitorRule) rule).resourceRule); + } + return false; + } + + @Override + public boolean contains(ISchedulingRule rule) { + if (rule instanceof MonitorRule) { + return resourceRule.contains(((MonitorRule) rule).resourceRule); + } + return false; + } + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + runnable.run(monitor); + } catch (CoreException e) { + IStatus st = e.getStatus(); + return new Status(st.getSeverity(), st.getPlugin(), st.getCode(), st.getMessage(), e); + } + return Status.OK_STATUS; + } + + @Override + public boolean belongsTo(Object family) { + return ResourcesPlugin.FAMILY_AUTO_REFRESH == family; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java new file mode 100644 index 0000000000..2a31a12fda --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java @@ -0,0 +1,378 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.refresh; + +import java.util.*; +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. This field is guarded by this as + * it may be read and written by several threads. + */ + 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() { + synchronized (this) { + 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); + } + synchronized (this) { + 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, new NullProgressMonitor()); + break; + } + } + + private boolean isMonitoring(IResource resource) { + synchronized (registeredMonitors) { + for (List resources : registeredMonitors.values()) { + 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, IProgressMonitor progressMonitor) { + if (isMonitoring(resource)) + return false; + boolean pollingMonitorNeeded = true; + RefreshProvider[] refreshProviders = getRefreshProviders(); + SubMonitor subMonitor = SubMonitor.convert(progressMonitor, refreshProviders.length); + for (int i = 0; i < refreshProviders.length; i++) { + IRefreshMonitor monitor = safeInstallMonitor(refreshProviders[i], resource, subMonitor.split(1)); + 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 (IResource resource1 : resources) { + pollMonitor.monitor(resource1); + registerMonitor(pollMonitor, resource1); + } + registeredMonitors.remove(monitor); + } + } else { + removeMonitor(monitor, resource); + pollMonitor.monitor(resource); + registerMonitor(pollMonitor, resource); + } + } + + /** + * @see IPathVariableChangeListener#pathVariableChanged(IPathVariableChangeEvent) + */ + @Override + public void pathVariableChanged(IPathVariableChangeEvent event) { + if (registeredMonitors.isEmpty()) + return; + String variableName = event.getVariableName(); + final Set invalidResources = new HashSet<>(); + for (List resources : registeredMonitors.values()) { + for (IResource resource : resources) { + 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()) { + MonitorJob.createSystem(Messages.refresh_restoreOnInvalid, invalidResources, new ICoreRunnable() { + @Override + public void run(IProgressMonitor monitor) { + SubMonitor subMonitor = SubMonitor.convert(monitor, invalidResources.size() * 2); + for (IResource resource : invalidResources) { + unmonitor(resource, subMonitor.split(1)); + monitor(resource, subMonitor.split(1)); + // Because the monitor is installed asynchronously we + // may have missed some changes, we need to refresh it. + refreshManager.refresh(resource); + } + } + }).schedule(); + } + } + + 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, IProgressMonitor progressMonitor) { + Throwable t = null; + try { + return provider.installMonitor(resource, refreshManager, progressMonitor); + } catch (Exception | 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. + * @param progressMonitor + */ + public void start(IProgressMonitor progressMonitor) { + List resourcesToMonitor = getResourcesToMonitor(); + SubMonitor subMonitor = SubMonitor.convert(progressMonitor, resourcesToMonitor.size() + 1); + boolean refreshNeeded = false; + for (IResource resource : resourcesToMonitor) { + refreshNeeded |= !monitor(resource, subMonitor.split(1)); + } + 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. + subMonitor.split(1); + 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 (IRefreshMonitor monitor : registeredMonitors.keySet()) { + 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, IProgressMonitor progressMonitor) { + if (resource == null || !isMonitoring(resource)) + return; + SubMonitor subMonitor = SubMonitor.convert(progressMonitor, 100); + synchronized (registeredMonitors) { + SubMonitor loopMonitor = subMonitor.split(90).setWorkRemaining(registeredMonitors.entrySet().size()); + for (Map.Entry> entry : registeredMonitors.entrySet()) { + loopMonitor.worked(1); + List resources = entry.getValue(); + if (resources != null && resources.contains(resource)) { + entry.getKey().unmonitor(resource); + resources.remove(resource); + } + } + } + if (resource.getType() == IResource.PROJECT) + unmonitorLinkedContents((IProject) resource, subMonitor.split(10)); + } + + private void unmonitorLinkedContents(IProject project, IProgressMonitor progressMonitor) { + 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) { + SubMonitor subMonitor = SubMonitor.convert(progressMonitor, children.length); + for (int i = 0; i < children.length; i++) { + if (children[i].isLinked()) { + unmonitor(children[i], subMonitor.split(1)); + } + } + } + } + + @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, new NullProgressMonitor()); + } + if ((delta.getFlags() & IResourceDelta.OPEN) != 0) { + IProject project = (IProject) delta.getResource(); + if (project.isAccessible()) + monitor(project, new NullProgressMonitor()); + } + return true; + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java new file mode 100644 index 0000000000..72a03fa9f2 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java @@ -0,0 +1,219 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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 + Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES); + if (bundle != null && bundle.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$ + } + + @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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java new file mode 100644 index 0000000000..0b534f48b3 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java @@ -0,0 +1,217 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427, 483862 + *******************************************************************************/ +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.*; + +/** + * 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); + } + + @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); + } + + /** + * @see org.eclipse.core.resources.refresh.IRefreshResult#refresh + */ + public void refresh(IResource resource) { + if (resource == null) + return; + addRequest(resource); + schedule(UPDATE_DELAY); + } + + @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; + SubMonitor subMonitor = SubMonitor.convert(monitor); + try { + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(RefreshManager.DEBUG_PREFIX + " starting refresh job"); //$NON-NLS-1$ + int refreshCount = 0; + int depth = 2; + + IResource toRefresh; + while ((toRefresh = nextRequest()) != null) { + try { + subMonitor.setWorkRemaining(Math.max(fRequests.size(), 100)); + refreshCount++; + long refreshTime = -System.currentTimeMillis(); + toRefresh.refreshLocal(1000 + depth, subMonitor.split(1)); + refreshTime += System.currentTimeMillis(); + if (refreshTime > longestRefresh) + longestRefresh = refreshTime; + //show occasional progress + 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; + 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; + } + + @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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java new file mode 100644 index 0000000000..a7c8963c8a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * 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.internal.utils.Messages; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.refresh.IRefreshMonitor; +import org.eclipse.core.resources.refresh.IRefreshResult; +import org.eclipse.core.runtime.*; +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, IProgressMonitor progressMonitor) { + //do nothing if we have already shutdown + if (refreshJob == null) { + return; + } + SubMonitor subMonitor = SubMonitor.convert(progressMonitor, 1); + if (enabled) { + refreshJob.start(); + monitors.start(subMonitor.split(1)); + } 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(); + final boolean autoRefresh = preferences.getBoolean(ResourcesPlugin.PREF_AUTO_REFRESH); + String jobName = autoRefresh ? Messages.refresh_installMonitorsOnWorkspace : Messages.refresh_uninstallMonitorsOnWorkspace; + MonitorJob.createSystem(jobName, ResourcesPlugin.getWorkspace().getRoot(), new ICoreRunnable() { + @Override + public void run(IProgressMonitor monitor) { + manageAutoRefresh(autoRefresh, monitor); + } + }).schedule(); + } + } + + @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) { + SubMonitor subMonitor = SubMonitor.convert(monitor, 1); + 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, subMonitor.split(1)); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java new file mode 100644 index 0000000000..0c5c42c293 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java @@ -0,0 +1,762 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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; + } + } + } + + @Override + public void shutdown(IProgressMonitor monitor) { + workspace.removeResourceChangeListener(this); + locationsMap.clear(); + } + + @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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java new file mode 100644 index 0000000000..235eb77971 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/BuildConfiguration.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * 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); + } + + @Override + public String getName() { + return name; + } + + @Override + public IProject getProject() { + return project; + } + + @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; + } + + @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; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java new file mode 100644 index 0000000000..5d135608df --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java @@ -0,0 +1,212 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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(); + } + } + + @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); + } + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java new file mode 100644 index 0000000000..cd60ff9041 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java @@ -0,0 +1,502 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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); + } + } + + @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); + } + } catch (CoreException ce) { + return ce.getStatus(); + } finally { + monitor.done(); + } + return result; + } + + @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 (Map.Entry entry : affectedResourcesMap.entrySet()) { + Boolean isDerived = entry.getKey(); + String[] affectedResources = entry.getValue(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java new file mode 100644 index 0000000000..be7a269f9a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java @@ -0,0 +1,621 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Broadcom Corporation - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = NEXT_VERTEX; + final Integer AFTER_NEXTED_DFS_VISIT_OBJECT = 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 vertexes, List 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 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 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java new file mode 100644 index 0000000000..7055babd49 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java @@ -0,0 +1,387 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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); + } + } 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); + } + } 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); + } + } finally { + monitor.done(); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java new file mode 100644 index 0000000000..ee4d86e64b --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java @@ -0,0 +1,549 @@ +/******************************************************************************* + * 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 + * Sergey Prigogin (Google) - [464072] Refresh on Access ignored during text search + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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); + } + + @Override + public boolean belongsTo(Object family) { + return FAMILY_DESCRIPTION_CACHE_FLUSH.equals(family); + } + + @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); + } + } 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) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } + 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 (FileNotFoundException e) { + String message = NLS.bind(Messages.localstore_fileNotFound, file.getFullPath()); + throw new ResourceException(IResourceStatus.RESOURCE_NOT_FOUND, file.getFullPath(), message, e); + } 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java new file mode 100644 index 0000000000..e45d735926 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java new file mode 100644 index 0000000000..0f13bddef5 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java @@ -0,0 +1,444 @@ +/******************************************************************************* + * 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 + * Sergey Prigogin (Google) - [464072] Refresh on Access ignored during text search + *******************************************************************************/ +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); + } + } 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); + } + } 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); + try { + return internalGetCharset(checkImplicit, info); + } catch (CoreException e) { + if (e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND) { + return checkImplicit ? workspace.getCharsetManager().getCharsetFor(getFullPath().removeLastSegments(1), true) : null; + } + throw e; + } + } + + @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); + } + } 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); + } + } 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java new file mode 100644 index 0000000000..ef24960ac1 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * 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; + } + + @Override + public boolean exists() { + return store.exists(this); + } + + @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); + } + } + + @Override + public InputStream getContents() throws CoreException { + return store.getContents(this); + } + + @Override + public IPath getFullPath() { + return fullPath; + } + + @Override + public long getModificationTime() { + return lastModified; + } + + @Override + public String getName() { + return fullPath.lastSegment(); + } + + public UniversalUniqueIdentifier getUUID() { + return uuid; + } + + @Override + public boolean isReadOnly() { + return true; + } + + /** + * Returns a string representation of this object. Used for debug only. + */ + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Filter.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java new file mode 100644 index 0000000000..88cd8d795b --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescription.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java new file mode 100644 index 0000000000..d6cf7fcf8d --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterDescriptor.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * 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$ + } + + @Override + public String getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getArgumentType() { + return argumentType; + } + + public AbstractFileInfoMatcher createFilter() { + try { + return (AbstractFileInfoMatcher) element.createExecutableExtension("class"); //$NON-NLS-1$ + } catch (CoreException e) { + Policy.log(e); + return null; + } + } + + @Override + public boolean isFirstOrdering() { + return isFirst; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java new file mode 100644 index 0000000000..78ba4d5a01 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FilterTypeManager.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java new file mode 100644 index 0000000000..039f13dbcb --- /dev/null +++ b/third_party/patches/oxygen/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); + } + } 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java new file mode 100644 index 0000000000..c82bfb9dc8 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IManager.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IMarkerSetElement.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java new file mode 100644 index 0000000000..5db12b9fd9 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * 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 {@link 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 { + /** + * Internal implementation of {@link TeamHook#setRuleFactory(IProject, IResourceRuleFactory)}. + */ + @SuppressWarnings("javadoc") // Suppress the "method in not visible" warning. + protected void setRuleFactory(IProject project, IResourceRuleFactory factory) { + Workspace workspace = ((Workspace) project.getWorkspace()); + ((Rules) workspace.getRuleFactory()).setRuleFactory(project, factory); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalWorkspaceJob.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalWorkspaceJob.java new file mode 100644 index 0000000000..5cbdf5d580 --- /dev/null +++ b/third_party/patches/oxygen/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); + } + } catch (CoreException e) { + return e.getStatus(); + } + } + + protected abstract IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java new file mode 100644 index 0000000000..070d3d9b5d --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java new file mode 100644 index 0000000000..2ec5c47588 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java @@ -0,0 +1,514 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * 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 + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = Integer.parseInt(sequenceNumber) + 1; + n = n < 0 ? 1 : n; + sequenceNumber = Integer.toString(n); + getWorkspace().getSaveManager().getMasterTable().setProperty(key.toString(), sequenceNumber); + } + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java new file mode 100644 index 0000000000..0a3a68a0d7 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java new file mode 100644 index 0000000000..95e82cc296 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java @@ -0,0 +1,372 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.text.DateFormat; +import java.util.*; +import java.util.Map.Entry; +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); + } + } + + /** + * @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, Integer.valueOf(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); + } + } + + /** + * @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); + } + } + + /** + * @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); + } + } + + /** For debugging only */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Marker ["); //$NON-NLS-1$ + sb.append("on: ").append(resource.getFullPath()); //$NON-NLS-1$ + MarkerInfo info = getInfo(); + if (info == null) { + sb.append(", not found]"); //$NON-NLS-1$ + return sb.toString(); + } + sb.append(", id: ").append(info.getId()); //$NON-NLS-1$ + sb.append(", type: ").append(info.getType()); //$NON-NLS-1$ + Map attributes = info.getAttributes(); + if (attributes != null) { + TreeMap tm = new TreeMap<>(attributes); + Set> set = tm.entrySet(); + if (!set.isEmpty()) { + sb.append(", attributes: ["); //$NON-NLS-1$ + for (Entry entry : set) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append(", "); //$NON-NLS-1$ //$NON-NLS-2$ + } + sb.setLength(sb.length() - 2); + sb.append(']'); + } + } + sb.append(", created: ").append(DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(new Date(info.getCreationTime()))); //$NON-NLS-1$ + sb.append(']'); + return sb.toString(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java new file mode 100644 index 0000000000..bac49bfb96 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java @@ -0,0 +1,295 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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); + } + + @Override + public void clear() { + count = 0; + elements = EMPTY; + } + + @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; + } + + @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; + } + + /** + * 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(); + } + + @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; + } + + @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; + } + + @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; + } + + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * 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; + } + + @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; + } + + @Override + public void putAll(Map map) { + for (Map.Entry e : map.entrySet()) + put(e.getKey(), e.getValue()); + } + + @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; + } + + @Override + public int size() { + return count; + } + + @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; + } + + /** + * 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java new file mode 100644 index 0000000000..606788d9da --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.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.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; + } + + @Override + public Object getAttribute(String attributeName) { + return info.getAttribute(attributeName); + } + + @Override + public int getAttribute(String attributeName, int defaultValue) { + Object value = info.getAttribute(attributeName); + if (value instanceof Integer) + return ((Integer) value).intValue(); + return defaultValue; + } + + @Override + public String getAttribute(String attributeName, String defaultValue) { + Object value = info.getAttribute(attributeName); + if (value instanceof String) + return (String) value; + return defaultValue; + } + + @Override + public boolean getAttribute(String attributeName, boolean defaultValue) { + Object value = info.getAttribute(attributeName); + if (value instanceof Boolean) + return ((Boolean) value).booleanValue(); + return defaultValue; + } + + @Override + public Map getAttributes() { + return info.getAttributes(); + } + + @Override + public Object[] getAttributes(String[] attributeNames) { + return info.getAttributes(attributeNames); + } + + @Override + public long getId() { + return info.getId(); + } + + @Override + public int getKind() { + return kind; + } + + @Override + public IMarker getMarker() { + return new Marker(resource, getId()); + } + + @Override + public IResource getResource() { + return resource; + } + + @Override + public String getType() { + return info.getType(); + } + + @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 (Map.Entry newEntry : newChanges.entrySet()) { + IPath key = newEntry.getKey(); + MarkerSet oldSet = oldChanges.get(key); + MarkerSet newSet = newEntry.getValue(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java new file mode 100644 index 0000000000..fb77faf894 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java new file mode 100644 index 0000000000..5b4b1ae0d0 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * Copyright (c) 2000, 2017 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 + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488938, 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Map.Entry; +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 = 1; + protected static final Integer INTEGER_TWO = 2; + protected static final Integer INTEGER_ZERO = 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 = valueString.getBytes(StandardCharsets.UTF_8); + 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 (Entry entry : map.entrySet()) { + Object key = entry.getKey(); + Assert.isTrue(key instanceof String); + Object value = entry.getValue(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java new file mode 100644 index 0000000000..59f823a7ac --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java @@ -0,0 +1,669 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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; + } + + /** + * 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); + } + + @Override + public void shutdown(IProgressMonitor monitor) { + // do nothing + } + + public void snap(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { + writer.snap(info, requestor, output); + } + + @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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java new file mode 100644 index 0000000000..b14d82cd5b --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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, 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java new file mode 100644 index 0000000000..775f2acf4e --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = input.readInt(); + break; + case ATTRIBUTE_BOOLEAN : + value = input.readBoolean(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java new file mode 100644 index 0000000000..755da7b188 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = input.readInt(); + break; + case ATTRIBUTE_BOOLEAN : + value = input.readBoolean(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java new file mode 100644 index 0000000000..0400cf8a3d --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = intValue; + } + break; + case ATTRIBUTE_BOOLEAN : + value = input.readBoolean(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSet.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java new file mode 100644 index 0000000000..bf980b2ad4 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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, 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java new file mode 100644 index 0000000000..583876eb69 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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 = input.readInt(); + break; + case ATTRIBUTE_BOOLEAN : + value = input.readBoolean(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java new file mode 100644 index 0000000000..d7f030e2aa --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +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<>(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(); + 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 = intValue; + } + break; + case ATTRIBUTE_BOOLEAN : + value = input.readBoolean(); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java new file mode 100644 index 0000000000..6a981c9213 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; + +public class MarkerTypeDefinitionCache { + static class MarkerTypeDefinition { + boolean isPersistent = false; + Set superTypes; + + MarkerTypeDefinition(IExtension ext) { + IConfigurationElement[] elements = ext.getConfigurationElements(); + for (int i = 0; i < elements.length; i++) { + IConfigurationElement element = elements[i]; + // supertype + final String elementName = element.getName(); + if (elementName.equalsIgnoreCase("super")) { //$NON-NLS-1$ + String aType = element.getAttribute("type"); //$NON-NLS-1$ + if (aType != null) { + if (superTypes == null) + superTypes = new HashSet<>(8); + //note that all marker type names will be in the intern table + //already because there is invariably a constant to describe + //the type name + superTypes.add(aType.intern()); + } + } + // persistent + if (elementName.equalsIgnoreCase("persistent")) { //$NON-NLS-1$ + String bool = element.getAttribute("value"); //$NON-NLS-1$ + if (bool != null) + this.isPersistent = Boolean.valueOf(bool).booleanValue(); + } + // XXX: legacy code for support of tag. remove later. + if (elementName.equalsIgnoreCase("transient")) { //$NON-NLS-1$ + String bool = element.getAttribute("value"); //$NON-NLS-1$ + if (bool != null) + this.isPersistent = !Boolean.valueOf(bool).booleanValue(); + } + } + } + } + + /** + * The marker type definitions. Maps String (markerId) -> MarkerTypeDefinition + */ + protected HashMap definitions; + + /** Constructs a new type cache. + */ + public MarkerTypeDefinitionCache() { + loadDefinitions(); + HashSet toCompute = new HashSet<>(definitions.keySet()); + for (Iterator i = definitions.keySet().iterator(); i.hasNext();) { + String markerId = i.next(); + if (toCompute.contains(markerId)) + computeSuperTypes(markerId, toCompute); + } + } + + /** + * Computes the transitive set of super types of the given marker type. + * @param markerId The type to compute super types for + * @param toCompute The set of types that have not yet had their + * supertypes computed. + * @return The transitive set of super types for this marker, or null + * if this marker is not defined or has no super types. + */ + private Set computeSuperTypes(String markerId, Set toCompute) { + MarkerTypeDefinition def = definitions.get(markerId); + if (def == null || def.superTypes == null) { + //nothing to do if there are no supertypes + toCompute.remove(markerId); + return null; + } + Set transitiveSuperTypes = new HashSet<>(def.superTypes); + for (Iterator it = def.superTypes.iterator(); it.hasNext();) { + String superId = it.next(); + Set toAdd = null; + if (toCompute.contains(superId)) { + //this type's super types have not been compute yet. Do recursive call + toAdd = computeSuperTypes(superId, toCompute); + } else { + // we have already computed this super type's super types (or it doesn't exist) + MarkerTypeDefinition parentDef = definitions.get(superId); + if (parentDef != null) + toAdd = parentDef.superTypes; + } + if (toAdd != null) + transitiveSuperTypes.addAll(toAdd); + } + def.superTypes = transitiveSuperTypes; + toCompute.remove(markerId); + return transitiveSuperTypes; + } + + /** + * Returns true if the given marker type is defined to be persistent. + */ + public boolean isPersistent(String type) { + MarkerTypeDefinition def = definitions.get(type); + return def != null && def.isPersistent; + } + + /** + * Returns true if the given target class has the specified type as a super type. + */ + public boolean isSubtype(String type, String superType) { + //types are considered super types of themselves + if (type.equals(superType)) + return true; + MarkerTypeDefinition def = definitions.get(type); + return def != null && def.superTypes != null && def.superTypes.contains(superType); + } + + private void loadDefinitions() { + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MARKERS); + IExtension[] types = point.getExtensions(); + definitions = new HashMap<>(types.length); + for (int i = 0; i < types.length; i++) { + String markerId = types[i].getUniqueIdentifier(); + if (markerId != null) + definitions.put(markerId.intern(), new MarkerTypeDefinition(types[i])); + else + Policy.log(IStatus.WARNING, "Missing marker id from plugin: " + types[i].getContributor().getName(), null); //$NON-NLS-1$ + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java new file mode 100644 index 0000000000..7cf7af0aaf --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerWriter.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; +import org.eclipse.core.internal.watson.IPathRequestor; + +// +public class MarkerWriter { + + protected MarkerManager manager; + + // version numbers + public static final int MARKERS_SAVE_VERSION = 3; + public static final int MARKERS_SNAP_VERSION = 2; + + // 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 MarkerWriter(MarkerManager manager) { + super(); + this.manager = manager; + } + + /** + * Returns an Object array of length 2. The first element is an Integer which is the number + * of persistent markers found. The second element is an array of boolean values, with a + * value of true meaning that the marker at that index is to be persisted. + */ + private Object[] filterMarkers(IMarkerSetElement[] markers) { + Object[] result = new Object[2]; + boolean[] isPersistent = new boolean[markers.length]; + int count = 0; + for (int i = 0; i < markers.length; i++) { + MarkerInfo info = (MarkerInfo) markers[i]; + if (manager.isPersistent(info)) { + isPersistent[i] = true; + count++; + } + } + result[0] = count; + result[1] = isPersistent; + return result; + } + + /** + * 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 + * + */ + public void save(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List writtenTypes) throws IOException { + // phantom resources don't have markers + if (info.isSet(ICoreConstants.M_PHANTOM)) + return; + MarkerSet markers = info.getMarkers(false); + if (markers == null) + return; + IMarkerSetElement[] elements = markers.elements(); + // filter out the markers...determine if there are any persistent ones + Object[] result = filterMarkers(elements); + int count = ((Integer) result[0]).intValue(); + if (count == 0) + return; + // if this is the first set of markers that we have written, then + // write the version id for the file. + if (output.size() == 0) + output.writeInt(MARKERS_SAVE_VERSION); + boolean[] isPersistent = (boolean[]) result[1]; + output.writeUTF(requestor.requestPath().toString()); + output.writeInt(count); + for (int i = 0; i < elements.length; i++) + if (isPersistent[i]) + write((MarkerInfo) elements[i], output, writtenTypes); + } + + /** + * Snapshot the markers for the specified resource to the given output stream. + * + * 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 + */ + public void snap(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { + // phantom resources don't have markers + if (info.isSet(ICoreConstants.M_PHANTOM)) + return; + if (!info.isSet(ICoreConstants.M_MARKERS_SNAP_DIRTY)) + return; + MarkerSet markers = info.getMarkers(false); + if (markers == null) + return; + IMarkerSetElement[] elements = markers.elements(); + // filter out the markers...determine if there are any persistent ones + Object[] result = filterMarkers(elements); + int count = ((Integer) result[0]).intValue(); + // write the version id for the snapshot. + output.writeInt(MARKERS_SNAP_VERSION); + boolean[] isPersistent = (boolean[]) result[1]; + output.writeUTF(requestor.requestPath().toString()); + // always write out the count...even if its zero. this will help + // use pick up marker deletions from our snapshot. + output.writeInt(count); + List writtenTypes = new ArrayList<>(); + for (int i = 0; i < elements.length; i++) + if (isPersistent[i]) + write((MarkerInfo) elements[i], output, writtenTypes); + info.clear(ICoreConstants.M_MARKERS_SNAP_DIRTY); + } + + /* + * Write out the given marker attributes to the given output stream. + */ + private void write(Map attributes, DataOutputStream output) throws IOException { + output.writeShort(attributes.size()); + for (Map.Entry e : attributes.entrySet()) { + String key = e.getKey(); + output.writeUTF(key); + Object value = e.getValue(); + if (value instanceof Integer) { + output.writeByte(ATTRIBUTE_INTEGER); + output.writeInt(((Integer) value).intValue()); + continue; + } + if (value instanceof Boolean) { + output.writeByte(ATTRIBUTE_BOOLEAN); + output.writeBoolean(((Boolean) value).booleanValue()); + continue; + } + if (value instanceof String) { + output.writeByte(ATTRIBUTE_STRING); + output.writeUTF((String) value); + continue; + } + // otherwise we came across an attribute of an unknown type + // so just write out null since we don't know how to marshal it. + output.writeByte(ATTRIBUTE_NULL); + } + } + + private void write(MarkerInfo info, DataOutputStream output, List writtenTypes) throws IOException { + output.writeLong(info.getId()); + // if we have already written the type once, then write an integer + // constant to represent it instead to remove duplication + String type = info.getType(); + int index = writtenTypes.indexOf(type); + if (index == -1) { + output.writeByte(QNAME); + output.writeUTF(type); + writtenTypes.add(type); + } else { + output.writeByte(INDEX); + output.writeInt(index); + } + + // write out the size of the attribute table and + // then each attribute. + if (info.getAttributes(false) == null) { + output.writeShort(0); + } else + write(info.getAttributes(false), output); + + // write out the creation time + output.writeLong(info.getCreationTime()); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java new file mode 100644 index 0000000000..e60a839f17 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObject.java @@ -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 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +public abstract class ModelObject implements Cloneable { + protected String name; + + public ModelObject() { + super(); + } + + public ModelObject(String name) { + setName(name); + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; // won't happen + } + } + + public String getName() { + return name; + } + + public void setName(String value) { + name = value; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java new file mode 100644 index 0000000000..2939ff4f0f --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java @@ -0,0 +1,306 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.internal.localstore.SafeFileOutputStream; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; + +public class ModelObjectWriter implements IModelObjectConstants { + + /** + * Returns the string representing the serialized set of build triggers for + * the given command + */ + private static String triggerString(BuildCommand command) { + StringBuilder buf = new StringBuilder(); + if (command.isBuilding(IncrementalProjectBuilder.AUTO_BUILD)) + buf.append(TRIGGER_AUTO).append(','); + if (command.isBuilding(IncrementalProjectBuilder.CLEAN_BUILD)) + buf.append(TRIGGER_CLEAN).append(','); + if (command.isBuilding(IncrementalProjectBuilder.FULL_BUILD)) + buf.append(TRIGGER_FULL).append(','); + if (command.isBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD)) + buf.append(TRIGGER_INCREMENTAL).append(','); + return buf.toString(); + } + + public ModelObjectWriter() { + super(); + } + + protected String[] getReferencedProjects(ProjectDescription description) { + IProject[] projects = description.getReferencedProjects(); + String[] result = new String[projects.length]; + for (int i = 0; i < projects.length; i++) + result[i] = projects[i].getName(); + return result; + } + + protected void write(BuildCommand command, XMLWriter writer) { + writer.startTag(BUILD_COMMAND, null); + if (command != null) { + writer.printSimpleTag(NAME, command.getName()); + if (shouldWriteTriggers(command)) + writer.printSimpleTag(BUILD_TRIGGERS, triggerString(command)); + write(ARGUMENTS, command.getArguments(false), writer); + } + writer.endTag(BUILD_COMMAND); + } + + /** + * Returns whether the build triggers for this command should be written. + */ + private boolean shouldWriteTriggers(BuildCommand command) { + //only write triggers if command is configurable and there exists a trigger + //that the builder does NOT respond to. I.e., don't write out on the default + //cases to avoid dirtying .project files unnecessarily. + if (!command.isConfigurable()) + return false; + return !command.isBuilding(IncrementalProjectBuilder.AUTO_BUILD) || !command.isBuilding(IncrementalProjectBuilder.CLEAN_BUILD) || !command.isBuilding(IncrementalProjectBuilder.FULL_BUILD) || !command.isBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD); + } + + protected void write(LinkDescription description, XMLWriter writer) { + writer.startTag(LINK, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getProjectRelativePath()); + writer.printSimpleTag(TYPE, Integer.toString(description.getType())); + //use ASCII string of URI to ensure spaces are encoded + writeLocation(description.getLocationURI(), writer); + } + writer.endTag(LINK); + } + + protected void write(IResourceFilterDescription description, XMLWriter writer) { + writer.startTag(FILTER, null); + if (description != null) { + writer.printSimpleTag(ID, ((FilterDescription) description).getId()); + writer.printSimpleTag(NAME, description.getResource().getProjectRelativePath()); + writer.printSimpleTag(TYPE, Integer.toString(description.getType())); + if (description.getFileInfoMatcherDescription() != null) { + write(description.getFileInfoMatcherDescription(), writer); + } + } + writer.endTag(FILTER); + } + + protected void write(FileInfoMatcherDescription description, XMLWriter writer) { + writer.startTag(MATCHER, null); + writer.printSimpleTag(ID, description.getId()); + if (description.getArguments() != null) { + if (description.getArguments() instanceof String) { + writer.printSimpleTag(ARGUMENTS, description.getArguments()); + } else if (description.getArguments() instanceof FileInfoMatcherDescription[]) { + writer.startTag(ARGUMENTS, null); + FileInfoMatcherDescription[] array = (FileInfoMatcherDescription[]) description.getArguments(); + for (int i = 0; i < array.length; i++) { + write(array[i], writer); + } + writer.endTag(ARGUMENTS); + } else + writer.printSimpleTag(ARGUMENTS, ""); //$NON-NLS-1$ + } + writer.endTag(MATCHER); + } + + protected void write(VariableDescription description, XMLWriter writer) { + writer.startTag(VARIABLE, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getName()); + writer.printSimpleTag(VALUE, description.getValue()); + } + writer.endTag(VARIABLE); + } + + /** + * Writes a location to the XML writer. For backwards compatibility, + * local file system locations are written and read using a different tag + * from non-local file systems. + * @param location + * @param writer + */ + private void writeLocation(URI location, XMLWriter writer) { + if (EFS.SCHEME_FILE.equals(location.getScheme())) { + writer.printSimpleTag(LOCATION, FileUtil.toPath(location).toPortableString()); + } else { + writer.printSimpleTag(LOCATION_URI, location.toASCIIString()); + } + } + + /** + * The parameter tempLocation is a location to place our temp file (copy of the target one) + * to be used in case we could not successfully write the new file. + */ + public void write(Object object, IPath location, IPath tempLocation, String lineSeparator) throws IOException { + SafeFileOutputStream file = null; + String tempPath = tempLocation == null ? null : tempLocation.toOSString(); + try { + file = new SafeFileOutputStream(location.toOSString(), tempPath); + write(object, file, lineSeparator); + file.close(); + } finally { + FileUtil.safeClose(file); + } + } + + /** + * The OutputStream is closed in this method. + */ + public void write(Object object, OutputStream output, String lineSeparator) throws IOException { + try { + XMLWriter writer = new XMLWriter(output, lineSeparator); + write(object, writer); + writer.flush(); + writer.close(); + if (writer.checkError()) + throw new IOException(); + } finally { + FileUtil.safeClose(output); + } + } + + protected void write(Object obj, XMLWriter writer) throws IOException { + if (obj instanceof BuildCommand) { + write((BuildCommand) obj, writer); + return; + } + if (obj instanceof ProjectDescription) { + write((ProjectDescription) obj, writer); + return; + } + if (obj instanceof WorkspaceDescription) { + write((WorkspaceDescription) obj, writer); + return; + } + if (obj instanceof LinkDescription) { + write((LinkDescription) obj, writer); + return; + } + if (obj instanceof IResourceFilterDescription) { + write((IResourceFilterDescription) obj, writer); + return; + } + if (obj instanceof VariableDescription) { + write((VariableDescription) obj, writer); + return; + } + writer.printTabulation(); + writer.println(obj.toString()); + } + + protected void write(ProjectDescription description, XMLWriter writer) throws IOException { + writer.startTag(PROJECT_DESCRIPTION, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getName()); + String comment = description.getComment(); + writer.printSimpleTag(COMMENT, comment == null ? "" : comment); //$NON-NLS-1$ + URI snapshotLocation = description.getSnapshotLocationURI(); + if (snapshotLocation != null) { + writer.printSimpleTag(SNAPSHOT_LOCATION, snapshotLocation.toString()); + } + write(PROJECTS, PROJECT, getReferencedProjects(description), writer); + write(BUILD_SPEC, Arrays.asList(description.getBuildSpec(false)), writer); + write(NATURES, NATURE, description.getNatureIds(false), writer); + HashMap links = description.getLinks(); + if (links != null) { + // ensure consistent order of map elements + List sorted = new ArrayList<>(links.values()); + Collections.sort(sorted); + write(LINKED_RESOURCES, sorted, writer); + } + HashMap> filters = description.getFilters(); + if (filters != null) { + List sorted = new ArrayList<>(); + for (Iterator> it = filters.values().iterator(); it.hasNext();) { + List list = it.next(); + sorted.addAll(list); + } + Collections.sort(sorted); + write(FILTERED_RESOURCES, sorted, writer); + } + HashMap variables = description.getVariables(); + if (variables != null) { + List sorted = new ArrayList<>(variables.values()); + Collections.sort(sorted); + write(VARIABLE_LIST, sorted, writer); + } + } + writer.endTag(PROJECT_DESCRIPTION); + } + + protected void write(String name, Collection collection, XMLWriter writer) throws IOException { + writer.startTag(name, null); + for (Object o : collection) + write(o, writer); + writer.endTag(name); + } + + /** + * Write maps of (String, String). + */ + protected void write(String name, Map table, XMLWriter writer) { + writer.startTag(name, null); + if (table != null) { + // ensure consistent order of map elements + List sorted = new ArrayList<>(table.keySet()); + Collections.sort(sorted); + + for (Iterator it = sorted.iterator(); it.hasNext();) { + String key = it.next(); + Object value = table.get(key); + writer.startTag(DICTIONARY, null); + { + writer.printSimpleTag(KEY, key); + writer.printSimpleTag(VALUE, value); + } + writer.endTag(DICTIONARY); + } + } + writer.endTag(name); + } + + protected void write(String name, String elementTagName, String[] array, XMLWriter writer) { + writer.startTag(name, null); + for (int i = 0; i < array.length; i++) + writer.printSimpleTag(elementTagName, array[i]); + writer.endTag(name); + } + + protected void write(WorkspaceDescription description, XMLWriter writer) { + writer.startTag(WORKSPACE_DESCRIPTION, null); + if (description != null) { + writer.printSimpleTag(NAME, description.getName()); + writer.printSimpleTag(AUTOBUILD, description.isAutoBuilding() ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$ + writer.printSimpleTag(SNAPSHOT_INTERVAL, description.getSnapshotInterval()); + writer.printSimpleTag(APPLY_FILE_STATE_POLICY, description.isApplyFileStatePolicy() ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$ + writer.printSimpleTag(FILE_STATE_LONGEVITY, description.getFileStateLongevity()); + writer.printSimpleTag(MAX_FILE_STATE_SIZE, description.getMaxFileStateSize()); + writer.printSimpleTag(MAX_FILE_STATES, description.getMaxFileStates()); + String[] order = description.getBuildOrder(false); + if (order != null) + write(BUILD_ORDER, PROJECT, order, writer); + } + writer.endTag(WORKSPACE_DESCRIPTION); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java new file mode 100644 index 0000000000..4b369fa4a7 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MoveDeleteHook.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IMoveDeleteHook; +import org.eclipse.core.resources.team.IResourceTree; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * @since 2.0 + */ +public class MoveDeleteHook implements IMoveDeleteHook { + + /** + * @see IMoveDeleteHook#deleteFile(IResourceTree, IFile, int, IProgressMonitor) + */ + @Override + public boolean deleteFile(IResourceTree tree, IFile file, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#deleteFolder(IResourceTree, IFolder, int, IProgressMonitor) + */ + @Override + public boolean deleteFolder(IResourceTree tree, IFolder folder, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#deleteProject(IResourceTree, IProject, int, IProgressMonitor) + */ + @Override + public boolean deleteProject(IResourceTree tree, IProject project, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#moveFile(IResourceTree, IFile, IFile, int, IProgressMonitor) + */ + @Override + public boolean moveFile(IResourceTree tree, IFile source, IFile destination, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#moveFolder(IResourceTree, IFolder, IFolder, int, IProgressMonitor) + */ + @Override + public boolean moveFolder(final IResourceTree tree, IFolder source, IFolder destination, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + + /** + * @see IMoveDeleteHook#moveProject(IResourceTree, IProject, IProjectDescription, int, IProgressMonitor) + */ + @Override + public boolean moveProject(IResourceTree tree, IProject source, IProjectDescription description, int updateFlags, IProgressMonitor monitor) { + // Let someone else do the work. + return false; + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java new file mode 100644 index 0000000000..d14a368ae7 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/NatureManager.java @@ -0,0 +1,643 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.internal.events.ILifecycleListener; +import org.eclipse.core.internal.events.LifecycleEvent; +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; + +/** + * Maintains collection of known nature descriptors, and implements + * nature-related algorithms provided by the workspace. + */ +public class NatureManager implements ILifecycleListener, IManager { + //maps String (nature ID) -> descriptor objects + private Map descriptors; + + //maps IProject -> String[] of enabled natures for that project + private final Map natureEnablements = new HashMap<>(20); + + //maps String (builder ID) -> String (nature ID) + private Map buildersToNatures; + //colour constants used in cycle detection algorithm + private static final byte WHITE = 0; + private static final byte GREY = 1; + private static final byte BLACK = 2; + + protected NatureManager() { + super(); + } + + /** + * Computes the list of natures that are enabled for the given project. + * Enablement computation is subtly different from nature set + * validation, because it must find and remove all inconsistencies. + */ + protected String[] computeNatureEnablements(Project project) { + final ProjectDescription description = project.internalGetDescription(); + if (description == null) + return new String[0];//project deleted concurrently + String[] natureIds = description.getNatureIds(); + int count = natureIds.length; + if (count == 0) + return natureIds; + + //set of the nature ids being validated (String (id)) + HashSet candidates = new HashSet<>(count * 2); + //table of String(set ID) -> ArrayList (nature IDs that belong to that set) + HashMap> setsToNatures = new HashMap<>(count); + for (int i = 0; i < count; i++) { + String id = natureIds[i]; + ProjectNatureDescriptor desc = (ProjectNatureDescriptor) getNatureDescriptor(id); + if (desc == null) + continue; + if (!desc.hasCycle) + candidates.add(id); + //build set to nature map + String[] setIds = desc.getNatureSetIds(); + for (int j = 0; j < setIds.length; j++) { + String set = setIds[j]; + ArrayList current = setsToNatures.get(set); + if (current == null) { + current = new ArrayList<>(5); + setsToNatures.put(set, current); + } + current.add(id); + } + } + //now remove all natures that belong to sets with more than one member + for (Iterator> it = setsToNatures.values().iterator(); it.hasNext();) { + ArrayList setMembers = it.next(); + if (setMembers.size() > 1) { + candidates.removeAll(setMembers); + } + } + //now walk over the set and ensure all pre-requisite natures are present + //need to walk in prereq order because if A requires B and B requires C, and C is + //disabled for some other reason, we must ensure both A and B are disabled + String[] orderedCandidates = candidates.toArray(new String[candidates.size()]); + orderedCandidates = sortNatureSet(orderedCandidates); + for (int i = 0; i < orderedCandidates.length; i++) { + String id = orderedCandidates[i]; + IProjectNatureDescriptor desc = getNatureDescriptor(id); + String[] required = desc.getRequiredNatureIds(); + for (int j = 0; j < required.length; j++) { + if (!candidates.contains(required[j])) { + candidates.remove(id); + break; + } + } + } + //remaining candidates are enabled + return candidates.toArray(new String[candidates.size()]); + } + + /* (non-Javadoc) + * @see IWorkspace#getNatureDescriptor(String) + */ + public synchronized IProjectNatureDescriptor getNatureDescriptor(String natureId) { + lazyInitialize(); + return descriptors.get(natureId); + } + + /* (non-Javadoc) + * @see IWorkspace#getNatureDescriptors() + */ + public synchronized IProjectNatureDescriptor[] getNatureDescriptors() { + lazyInitialize(); + Collection values = descriptors.values(); + return values.toArray(new IProjectNatureDescriptor[values.size()]); + } + + @Override + public void handleEvent(LifecycleEvent event) { + switch (event.kind) { + case LifecycleEvent.POST_PROJECT_CHANGE : + case LifecycleEvent.PRE_PROJECT_CLOSE : + case LifecycleEvent.PRE_PROJECT_DELETE : + case LifecycleEvent.PRE_PROJECT_MOVE : + case LifecycleEvent.PRE_PROJECT_OPEN : + flushEnablements((IProject) event.resource); + } + } + + /** + * Configures the nature with the given ID for the given project. + */ + protected void configureNature(final Project project, final String natureID, final MultiStatus errors) { + ISafeRunnable code = new ISafeRunnable() { + @Override + public void run() throws Exception { + IProjectNature nature = createNature(project, natureID); + nature.configure(); + ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, true); + info.setNature(natureID, nature); + } + + @Override + public void handleException(Throwable exception) { + if (exception instanceof CoreException) + errors.add(((CoreException) exception).getStatus()); + else + errors.add(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, project.getFullPath(), NLS.bind(Messages.resources_errorNature, natureID), exception)); + } + }; + if (Policy.DEBUG_NATURES) { + Policy.debug("Configuring nature: " + natureID + " on project: " + project.getName()); //$NON-NLS-1$ //$NON-NLS-2$ + } + SafeRunner.run(code); + } + + /** + * Configures the natures for the given project. Natures found in the new description + * that weren't present in the old description are added, and natures missing from the + * new description are removed. Updates the old description so that it reflects + * the new set of the natures. Errors are added to the given multi-status. + */ + @SuppressWarnings({"unchecked"}) + public void configureNatures(Project project, ProjectDescription oldDescription, ProjectDescription newDescription, MultiStatus status) { + // Be careful not to rely on much state because (de)configuring a nature + // may well result in recursive calls to this method. + HashSet oldNatures = new HashSet<>(Arrays.asList(oldDescription.getNatureIds(false))); + HashSet newNatures = new HashSet<>(Arrays.asList(newDescription.getNatureIds(false))); + if (oldNatures.equals(newNatures)) + return; + HashSet deletions = (HashSet) oldNatures.clone(); + HashSet additions = (HashSet) newNatures.clone(); + additions.removeAll(oldNatures); + deletions.removeAll(newNatures); + //do validation of the changes. If any single change is invalid, fail the whole operation + IStatus result = validateAdditions(newNatures, additions, project); + if (!result.isOK()) { + status.merge(result); + return; + } + result = validateRemovals(newNatures, deletions); + if (!result.isOK()) { + status.merge(result); + return; + } + // set the list of nature ids BEFORE (de)configuration so recursive calls will + // not try to do the same work. + oldDescription.setNatureIds(newDescription.getNatureIds(true)); + flushEnablements(project); + //(de)configure in topological order to maintain consistency of configured set + String[] ordered = null; + if (deletions.size() > 0) { + ordered = sortNatureSet(deletions.toArray(new String[deletions.size()])); + for (int i = ordered.length; --i >= 0;) + deconfigureNature(project, ordered[i], status); + } + if (additions.size() > 0) { + ordered = sortNatureSet(additions.toArray(new String[additions.size()])); + for (int i = 0; i < ordered.length; i++) + configureNature(project, ordered[i], status); + } + } + + /** + * Finds the nature extension, and initializes and returns an instance. + */ + protected IProjectNature createNature(Project project, String natureID) throws CoreException { + IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_NATURES, natureID); + if (extension == null) { + String message = NLS.bind(Messages.resources_natureExtension, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null); + } + IConfigurationElement[] configs = extension.getConfigurationElements(); + if (configs.length < 1) { + String message = NLS.bind(Messages.resources_natureClass, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null); + } + //find the runtime configuration element + IConfigurationElement config = null; + for (int i = 0; config == null && i < configs.length; i++) + if ("runtime".equalsIgnoreCase(configs[i].getName())) //$NON-NLS-1$ + config = configs[i]; + if (config == null) { + String message = NLS.bind(Messages.resources_natureFormat, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null); + } + try { + IProjectNature nature = (IProjectNature) config.createExecutableExtension("run"); //$NON-NLS-1$ + nature.setProject(project); + return nature; + } catch (ClassCastException e) { + String message = NLS.bind(Messages.resources_natureImplement, natureID); + throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, e); + } + } + + /** + * Deconfigures the nature with the given ID for the given project. + */ + protected void deconfigureNature(final Project project, final String natureID, final MultiStatus status) { + final ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, true); + IProjectNature existingNature = info.getNature(natureID); + if (existingNature == null) { + // if there isn't a nature then create one so we can deconfig it. + try { + existingNature = createNature(project, natureID); + } catch (CoreException e) { + // Ignore - we are removing a nature that no longer exists in the install + return; + } + } + final IProjectNature nature = existingNature; + ISafeRunnable code = new ISafeRunnable() { + @Override + public void run() throws Exception { + nature.deconfigure(); + info.setNature(natureID, null); + } + + @Override + public void handleException(Throwable exception) { + if (exception instanceof CoreException) + status.add(((CoreException) exception).getStatus()); + else + status.add(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, project.getFullPath(), NLS.bind(Messages.resources_natureDeconfig, natureID), exception)); + } + }; + if (Policy.DEBUG_NATURES) { + Policy.debug("Deconfiguring nature: " + natureID + " on project: " + project.getName()); //$NON-NLS-1$ //$NON-NLS-2$ + } + SafeRunner.run(code); + } + + /** + * Marks all nature descriptors that are involved in cycles + */ + private void detectCycles() { + Collection values = descriptors.values(); + ProjectNatureDescriptor[] natures = values.toArray(new ProjectNatureDescriptor[values.size()]); + for (int i = 0; i < natures.length; i++) + if (natures[i].colour == WHITE) + hasCycles(natures[i]); + } + + /** + * Returns a status indicating failure to configure natures. + */ + protected IStatus failure(String reason) { + return new ResourceStatus(IResourceStatus.INVALID_NATURE_SET, reason); + } + + /** + * Returns the ID of the project nature that claims ownership of the + * builder with the given ID. Returns null if no nature owns that builder. + */ + public synchronized String findNatureForBuilder(String builderID) { + if (buildersToNatures == null) { + buildersToNatures = new HashMap<>(10); + IProjectNatureDescriptor[] descs = getNatureDescriptors(); + for (int i = 0; i < descs.length; i++) { + String natureId = descs[i].getNatureId(); + String[] builders = ((ProjectNatureDescriptor) descs[i]).getBuilderIds(); + for (int j = 0; j < builders.length; j++) { + //FIXME: how to handle multiple natures specifying same builder + buildersToNatures.put(builders[j], natureId); + } + } + } + return buildersToNatures.get(builderID); + } + + private synchronized void flushEnablements(IProject project) { + natureEnablements.remove(project); + } + + /** + * Returns the cached array of enabled natures for this project, + * or null if there is nothing in the cache. + */ + protected synchronized String[] getEnabledNatures(Project project) { + String[] enabled = natureEnablements.get(project); + if (enabled != null) + return enabled; + enabled = computeNatureEnablements(project); + natureEnablements.put(project, enabled); + return enabled; + } + + /** + * Returns true if there are cycles in the graph of nature + * dependencies starting at root i. Returns false otherwise. + * Marks all descriptors that are involved in the cycle as invalid. + */ + protected boolean hasCycles(ProjectNatureDescriptor desc) { + if (desc.colour == BLACK) { + //this subgraph has already been traversed, so we know the answer + return desc.hasCycle; + } + //if we are already grey, then we have found a cycle + if (desc.colour == GREY) { + desc.hasCycle = true; + desc.colour = BLACK; + return true; + } + //colour current descriptor GREY to indicate it is being visited + desc.colour = GREY; + + //visit all dependents of nature i + String[] required = desc.getRequiredNatureIds(); + for (int i = 0; i < required.length; i++) { + ProjectNatureDescriptor dependency = (ProjectNatureDescriptor) getNatureDescriptor(required[i]); + //missing dependencies cannot create cycles + if (dependency != null && hasCycles(dependency)) { + desc.hasCycle = true; + desc.colour = BLACK; + return true; + } + } + desc.hasCycle = false; + desc.colour = BLACK; + return false; + } + + /** + * Returns true if the given project has linked resources, and false otherwise. + */ + protected boolean hasLinks(IProject project) { + try { + IResource[] children = project.members(); + for (int i = 0; i < children.length; i++) + if (children[i].isLinked()) + return true; + } catch (CoreException e) { + //not possible for project to be inaccessible + Policy.log(e.getStatus()); + } + return false; + } + + /** + * Checks if the two natures have overlapping "one-of-nature" set + * memberships. Returns the name of one such overlap, or null if + * there is no set overlap. + */ + protected String hasSetOverlap(IProjectNatureDescriptor one, IProjectNatureDescriptor two) { + if (one == null || two == null) { + return null; + } + //efficiency not so important because these sets are very small + String[] setsOne = one.getNatureSetIds(); + String[] setsTwo = two.getNatureSetIds(); + for (int iOne = 0; iOne < setsOne.length; iOne++) { + for (int iTwo = 0; iTwo < setsTwo.length; iTwo++) { + if (setsOne[iOne].equals(setsTwo[iTwo])) { + return setsOne[iOne]; + } + } + } + return null; + } + + /** + * Perform depth-first insertion of the given nature ID into the result list. + */ + protected void insert(ArrayList list, Set seen, String id) { + if (seen.contains(id)) + return; + seen.add(id); + //insert prerequisite natures + IProjectNatureDescriptor desc = getNatureDescriptor(id); + if (desc != null) { + String[] prereqs = desc.getRequiredNatureIds(); + for (int i = 0; i < prereqs.length; i++) + insert(list, seen, prereqs[i]); + } + list.add(id); + } + + /* (non-Javadoc) + * Returns true if the given nature is enabled for the given project. + * + * @see IProject#isNatureEnabled(String) + */ + public boolean isNatureEnabled(Project project, String id) { + String[] enabled = getEnabledNatures(project); + for (int i = 0; i < enabled.length; i++) { + if (enabled[i].equals(id)) + return true; + } + return false; + } + + /** + * Only initialize the descriptor cache when we know it is actually needed. + * Running programs may never need to refer to this cache. + */ + private void lazyInitialize() { + if (descriptors != null) + return; + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_NATURES); + IExtension[] extensions = point.getExtensions(); + descriptors = new HashMap<>(extensions.length * 2 + 1); + for (int i = 0, imax = extensions.length; i < imax; i++) { + IProjectNatureDescriptor desc = null; + try { + desc = new ProjectNatureDescriptor(extensions[i]); + } catch (CoreException e) { + Policy.log(e.getStatus()); + } + if (desc != null) + descriptors.put(desc.getNatureId(), desc); + } + //do cycle detection now so it only has to be done once + //cycle detection on a graph subset is a pain + detectCycles(); + } + + @Override + public void shutdown(IProgressMonitor monitor) { + // do nothing + } + + /* (non-Javadoc) + * @see IWorkspace#sortNatureSet(String[]) + */ + public String[] sortNatureSet(String[] natureIds) { + int count = natureIds.length; + if (count == 0) + return natureIds; + ArrayList result = new ArrayList<>(count); + HashSet seen = new HashSet<>(count);//for cycle and duplicate detection + for (int i = 0; i < count; i++) + insert(result, seen, natureIds[i]); + //remove added prerequisites that didn't exist in original list + seen.clear(); + seen.addAll(Arrays.asList(natureIds)); + for (Iterator it = result.iterator(); it.hasNext();) { + Object id = it.next(); + if (!seen.contains(id)) + it.remove(); + } + return result.toArray(new String[result.size()]); + } + + @Override + public void startup(IProgressMonitor monitor) { + ((Workspace) ResourcesPlugin.getWorkspace()).addLifecycleListener(this); + } + + /** + * Validates the given nature additions in the nature set for this + * project. Tolerates existing inconsistencies in the nature set. + * @param newNatures the complete new set of nature IDs for the project, + * including additions + * @param additions the subset of newNatures that represents natures + * being added + * @return An OK status if all additions are valid, and an error status + * if any of the additions introduce new inconsistencies. + */ + protected IStatus validateAdditions(HashSet newNatures, HashSet additions, IProject project) { + Boolean hasLinks = null;//three states: true, false, null (not yet computed) + //perform checks in order from least expensive to most expensive + for (Iterator added = additions.iterator(); added.hasNext();) { + String id = added.next(); + // check for adding a nature that is not available. + IProjectNatureDescriptor desc = getNatureDescriptor(id); + if (desc == null) { + return failure(NLS.bind(Messages.natures_missingNature, id)); + } + // check for adding a nature that creates a circular dependency + if (((ProjectNatureDescriptor) desc).hasCycle) { + return failure(NLS.bind(Messages.natures_hasCycle, id)); + } + // check for adding a nature that has a missing prerequisite. + String[] required = desc.getRequiredNatureIds(); + for (int i = 0; i < required.length; i++) { + if (!newNatures.contains(required[i])) { + return failure(NLS.bind(Messages.natures_missingPrerequisite, id, required[i])); + } + } + // check for adding a nature that creates a duplicated set member. + for (Iterator all = newNatures.iterator(); all.hasNext();) { + String current = all.next(); + if (!current.equals(id)) { + String overlap = hasSetOverlap(desc, getNatureDescriptor(current)); + if (overlap != null) { + return failure(NLS.bind(Messages.natures_multipleSetMembers, overlap)); + } + } + } + //check for adding a nature that has linked resource veto + if (!desc.isLinkingAllowed()) { + if (hasLinks == null) { + hasLinks = hasLinks(project) ? Boolean.TRUE : Boolean.FALSE; + } + if (hasLinks.booleanValue()) + return failure(NLS.bind(Messages.links_vetoNature, project.getName(), id)); + } + } + return Status.OK_STATUS; + } + + /** + * Validates whether a project with the given set of natures should allow + * linked resources. Returns an OK status if linking is allowed, + * otherwise a non-OK status indicating why linking is not allowed. + * Linking is allowed if there is no project nature that explicitly disallows it. + * No validation is done on the nature ids themselves (ids that don't have + * a corresponding nature definition will be ignored). + */ + public IStatus validateLinkCreation(String[] natureIds) { + for (int i = 0; i < natureIds.length; i++) { + IProjectNatureDescriptor desc = getNatureDescriptor(natureIds[i]); + if (desc != null && !desc.isLinkingAllowed()) { + String msg = NLS.bind(Messages.links_natureVeto, desc.getLabel()); + return new ResourceStatus(IResourceStatus.LINKING_NOT_ALLOWED, msg); + } + } + return Status.OK_STATUS; + } + + /** + * Validates the given nature removals in the nature set for this + * project. Tolerates existing inconsistencies in the nature set. + * + * @param newNatures the complete new set of nature IDs for the project, + * excluding deletions + * @param deletions the nature IDs that are being removed from the set. + * @return An OK status if all removals are valid, and a not OK status + * if any of the deletions introduce new inconsistencies. + */ + protected IStatus validateRemovals(HashSet newNatures, HashSet deletions) { + //iterate over new nature set, and ensure that none of their prerequisites are being deleted + for (Iterator it = newNatures.iterator(); it.hasNext();) { + String currentID = it.next(); + IProjectNatureDescriptor desc = getNatureDescriptor(currentID); + if (desc != null) { + String[] required = desc.getRequiredNatureIds(); + for (int i = 0; i < required.length; i++) { + if (deletions.contains(required[i])) { + return failure(NLS.bind(Messages.natures_invalidRemoval, required[i], currentID)); + } + } + } + } + return Status.OK_STATUS; + } + + /* (non-Javadoc) + * @see IWorkspace#validateNatureSet(String[]) + */ + public IStatus validateNatureSet(String[] natureIds) { + int count = natureIds.length; + if (count == 0) + return Status.OK_STATUS; + String msg = Messages.natures_invalidSet; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_NATURE_SET, msg, null); + + //set of the nature ids being validated (String (id)) + HashSet natures = new HashSet<>(count * 2); + //set of nature sets for which a member nature has been found (String (id)) + HashSet sets = new HashSet<>(count); + for (int i = 0; i < count; i++) { + String id = natureIds[i]; + ProjectNatureDescriptor desc = (ProjectNatureDescriptor) getNatureDescriptor(id); + if (desc == null) { + result.add(failure(NLS.bind(Messages.natures_missingNature, id))); + continue; + } + if (desc.hasCycle) + result.add(failure(NLS.bind(Messages.natures_hasCycle, id))); + if (!natures.add(id)) + result.add(failure(NLS.bind(Messages.natures_duplicateNature, id))); + //validate nature set one-of constraint + String[] setIds = desc.getNatureSetIds(); + for (int j = 0; j < setIds.length; j++) { + if (!sets.add(setIds[j])) + result.add(failure(NLS.bind(Messages.natures_multipleSetMembers, setIds[j]))); + } + } + //now walk over the set and ensure all pre-requisite natures are present + for (int i = 0; i < count; i++) { + IProjectNatureDescriptor desc = getNatureDescriptor(natureIds[i]); + if (desc == null) + continue; + String[] required = desc.getRequiredNatureIds(); + for (int j = 0; j < required.length; j++) + if (!natures.contains(required[j])) + result.add(failure(NLS.bind(Messages.natures_missingPrerequisite, natureIds[i], required[j]))); + } + //if there are no problems we must return a status whose code is OK + return result.isOK() ? Status.OK_STATUS : (IStatus) result; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java new file mode 100644 index 0000000000..193ca3490c --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/OS.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Arrays; +import org.eclipse.core.runtime.Platform; + +/** + * Captures platform specific attributes relevant to the core resources plugin. This + * class is not intended to be instantiated. + */ +public abstract class OS { + private static final String INSTALLED_PLATFORM; + + public static final char[] INVALID_RESOURCE_CHARACTERS; + private static final String[] INVALID_RESOURCE_BASENAMES; + private static final String[] INVALID_RESOURCE_FULLNAMES; + + static { + //find out the OS being used + //setup the invalid names + INSTALLED_PLATFORM = Platform.getOS(); + if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) { + //valid names and characters taken from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp + INVALID_RESOURCE_CHARACTERS = new char[] {'\\', '/', ':', '*', '?', '"', '<', '>', '|'}; + INVALID_RESOURCE_BASENAMES = new String[] {"aux", "com1", "com2", "com3", "com4", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "com5", "com6", "com7", "com8", "com9", "con", "lpt1", "lpt2", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ + "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "nul", "prn"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ + Arrays.sort(INVALID_RESOURCE_BASENAMES); + //CLOCK$ may be used if an extension is provided + INVALID_RESOURCE_FULLNAMES = new String[] {"clock$"}; //$NON-NLS-1$ + } else { + //only front slash and null char are invalid on UNIXes + //taken from http://www.faqs.org/faqs/unix-faq/faq/part2/section-2.html + INVALID_RESOURCE_CHARACTERS = new char[] {'/', '\0',}; + INVALID_RESOURCE_BASENAMES = null; + INVALID_RESOURCE_FULLNAMES = null; + } + } + + /** + * Returns true if the given name is a valid resource name on this operating system, + * and false otherwise. + */ + public static boolean isNameValid(String name) { + //. and .. have special meaning on all platforms + if (name.equals(".") || name.equals("..")) //$NON-NLS-1$ //$NON-NLS-2$ + return false; + if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) { + //empty names are not valid + final int length = name.length(); + if (length == 0) + return false; + final char lastChar = name.charAt(length-1); + // filenames ending in dot are not valid + if (lastChar == '.') + return false; + // file names ending with whitespace are truncated (bug 118997) + if (Character.isWhitespace(lastChar)) + return false; + int dot = name.indexOf('.'); + //on windows, filename suffixes are not relevant to name validity + String basename = dot == -1 ? name : name.substring(0, dot); + if (Arrays.binarySearch(INVALID_RESOURCE_BASENAMES, basename.toLowerCase()) >= 0) + return false; + return Arrays.binarySearch(INVALID_RESOURCE_FULLNAMES, name.toLowerCase()) < 0; + } + return true; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java new file mode 100644 index 0000000000..bdafd8b3f4 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableManager.java @@ -0,0 +1,390 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.PathVariableChangeEvent; +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; + +/** + * Core's implementation of IPathVariableManager. + */ +public class PathVariableManager implements IPathVariableManager, IManager { + + static final String VARIABLE_PREFIX = "pathvariable."; //$NON-NLS-1$ + private Set listeners; + private Map> projectListeners; + + private Preferences preferences; + + /** + * Constructor for the class. + */ + public PathVariableManager() { + this.listeners = Collections.synchronizedSet(new HashSet()); + this.projectListeners = Collections.synchronizedMap(new HashMap>()); + this.preferences = ResourcesPlugin.getPlugin().getPluginPreferences(); + } + + /** + * @see org.eclipse.core.resources. + * IPathVariableManager#addChangeListener(IPathVariableChangeListener) + */ + @Override + public void addChangeListener(IPathVariableChangeListener listener) { + listeners.add(listener); + } + + synchronized public void addChangeListener(IPathVariableChangeListener listener, IProject project) { + Collection list = projectListeners.get(project); + if (list == null) { + list = Collections.synchronizedSet(new HashSet()); + projectListeners.put(project, list); + } + list.add(listener); + } + + /** + * Throws a runtime exception if the given name is not valid as a path + * variable name. + */ + private void checkIsValidName(String name) throws CoreException { + IStatus status = validateName(name); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * Throws an exception if the given path is not valid as a path variable + * value. + */ + private void checkIsValidValue(IPath newValue) throws CoreException { + IStatus status = validateValue(newValue); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * Fires a property change event corresponding to a change to the + * current value of the variable with the given name. + * + * @param name the name of the variable, to be used as the variable + * in the event object + * @param value the current value of the path variable or null if + * the variable was deleted + * @param type one of IPathVariableChangeEvent.VARIABLE_CREATED, + * IPathVariableChangeEvent.VARIABLE_CHANGED, or + * IPathVariableChangeEvent.VARIABLE_DELETED + * @see IPathVariableChangeEvent + * @see IPathVariableChangeEvent#VARIABLE_CREATED + * @see IPathVariableChangeEvent#VARIABLE_CHANGED + * @see IPathVariableChangeEvent#VARIABLE_DELETED + */ + private void fireVariableChangeEvent(String name, IPath value, int type) { + fireVariableChangeEvent(this.listeners, name, value, type); + } + + private void fireVariableChangeEvent(Collection list, String name, IPath value, int type) { + if (list.size() == 0) + return; + // use a separate collection to avoid interference of simultaneous additions/removals + Object[] listenerArray = list.toArray(); + final PathVariableChangeEvent pve = new PathVariableChangeEvent(this, name, value, type); + for (int i = 0; i < listenerArray.length; ++i) { + final IPathVariableChangeListener l = (IPathVariableChangeListener) listenerArray[i]; + ISafeRunnable job = new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + // already being logged in SafeRunner#run() + } + + @Override + public void run() throws Exception { + l.pathVariableChanged(pve); + } + }; + SafeRunner.run(job); + } + } + + public void fireVariableChangeEvent(IProject project, String name, IPath value, int type) { + Collection list = projectListeners.get(project); + if (list != null) + fireVariableChangeEvent(list, name, value, type); + } + + /** + * Return a key to use in the Preferences. + */ + private String getKeyForName(String varName) { + return VARIABLE_PREFIX + varName; + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#getPathVariableNames() + */ + @Override + public String[] getPathVariableNames() { + List result = new LinkedList<>(); + String[] names = preferences.propertyNames(); + for (int i = 0; i < names.length; i++) { + if (names[i].startsWith(VARIABLE_PREFIX)) { + String key = names[i].substring(VARIABLE_PREFIX.length()); + // filter out names for preferences which might be valid in the + // preference store but does not have valid path variable names + // and/or values. We can get in this state if the user has + // edited the file on disk or set a preference using the prefix + // reserved to path variables (#VARIABLE_PREFIX). + // TODO: we may want to look at removing these keys from the + // preference store as a garbage collection means + if (validateName(key).isOK() && validateValue(getValue(key)).isOK()) + result.add(key); + } + } + return result.toArray(new String[result.size()]); + } + + /** + * Note that if a user changes the key in the preferences file to be invalid + * and then calls #getValue using that key, they will get the value back for + * that. But then if they try and call #setValue using the same key it will throw + * an exception. We may want to revisit this behaviour in the future. + * + * @see org.eclipse.core.resources.IPathVariableManager#getValue(String) + */ + @Deprecated + @Override + public IPath getValue(String varName) { + String key = getKeyForName(varName); + String value = preferences.getString(key); + return value.length() == 0 ? null : Path.fromPortableString(value); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#isDefined(String) + */ + @Override + public boolean isDefined(String varName) { + return getValue(varName) != null; + } + + /** + * @see org.eclipse.core.resources. + * IPathVariableManager#removeChangeListener(IPathVariableChangeListener) + */ + @Override + public void removeChangeListener(IPathVariableChangeListener listener) { + listeners.remove(listener); + } + + synchronized public void removeChangeListener(IPathVariableChangeListener listener, IProject project) { + Collection list = projectListeners.get(project); + if (list != null) { + list.remove(listener); + if (list.isEmpty()) + projectListeners.remove(project); + } + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#resolvePath(IPath) + */ + @Deprecated + @Override + public IPath resolvePath(IPath path) { + if (path == null || path.segmentCount() == 0 || path.isAbsolute() || path.getDevice() != null) + return path; + IPath value = getValue(path.segment(0)); + return value == null ? path : value.append(path.removeFirstSegments(1)); + } + + @Override + public URI resolveURI(URI uri) { + if (uri == null || uri.isAbsolute()) + return uri; + if (uri.getSchemeSpecificPart() == null) + return uri; + IPath raw = new Path(uri.getSchemeSpecificPart()); + IPath resolved = resolvePath(raw); + return raw == resolved ? uri : URIUtil.toURI(resolved); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#setValue(String, IPath) + */ + @Override + public void setValue(String varName, IPath newValue) throws CoreException { + checkIsValidName(varName); + //convert path value to canonical form + if (newValue != null && newValue.isAbsolute()) + newValue = FileUtil.canonicalPath(newValue); + checkIsValidValue(newValue); + int eventType; + // read previous value and set new value atomically in order to generate the right event + synchronized (this) { + IPath currentValue = getValue(varName); + boolean variableExists = currentValue != null; + if (!variableExists && newValue == null) + return; + if (variableExists && currentValue.equals(newValue)) + return; + if (newValue == null) { + preferences.setToDefault(getKeyForName(varName)); + eventType = IPathVariableChangeEvent.VARIABLE_DELETED; + } else { + preferences.setValue(getKeyForName(varName), newValue.toPortableString()); + eventType = variableExists ? IPathVariableChangeEvent.VARIABLE_CHANGED : IPathVariableChangeEvent.VARIABLE_CREATED; + } + } + // notify listeners from outside the synchronized block to avoid deadlocks + fireVariableChangeEvent(varName, newValue, eventType); + } + + /** + * @see org.eclipse.core.internal.resources.IManager#shutdown(IProgressMonitor) + */ + @Override + public void shutdown(IProgressMonitor monitor) { + // The preferences for this plug-in are saved in the Plugin.shutdown + // method so we don't have to do it here. + } + + /** + * @see org.eclipse.core.internal.resources.IManager#startup(IProgressMonitor) + */ + @Override + public void startup(IProgressMonitor monitor) { + // since we are accessing the preference store directly, we don't + // need to do any setup here. + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#validateName(String) + */ + @Override + public IStatus validateName(String name) { + String message = null; + if (name.length() == 0) { + message = Messages.pathvar_length; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + char first = name.charAt(0); + if (!Character.isLetter(first) && first != '_') { + message = NLS.bind(Messages.pathvar_beginLetter, String.valueOf(first)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + for (int i = 1; i < name.length(); i++) { + char following = name.charAt(i); + if (Character.isWhitespace(following)) + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, Messages.pathvar_whitespace); + if (!Character.isLetter(following) && !Character.isDigit(following) && following != '_') { + message = NLS.bind(Messages.pathvar_invalidChar, String.valueOf(following)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + } + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#validateValue(IPath) + */ + @Override + public IStatus validateValue(IPath value) { + if (value != null && (!value.isValidPath(value.toString()) || !value.isAbsolute())) { + String message = Messages.pathvar_invalidValue; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#convertToRelative(URI, boolean, String) + */ + @Override + public URI convertToRelative(URI path, boolean force, String variableHint) throws CoreException { + return PathVariableUtil.convertToRelative(this, path, null, false, variableHint); + } + + /** + * see IPathVariableManager#getURIValue(String) + */ + @Override + public URI getURIValue(String name) { + IPath path = getValue(name); + if (path != null) + return URIUtil.toURI(path); + return null; + } + + /** + * see IPathVariableManager#setURIValue(String, URI) + */ + @Override + public void setURIValue(String name, URI value) throws CoreException { + setValue(name, (value != null ? URIUtil.toPath(value) : null)); + } + + /** + * @see IPathVariableManager#validateValue(URI) + */ + @Override + public IStatus validateValue(URI path) { + return validateValue(path != null ? URIUtil.toPath(path) : (IPath) null); + } + + public URI resolveURI(URI uri, IResource resource) { + return resolveURI(uri); + } + + public String[] getPathVariableNames(IResource resource) { + return getPathVariableNames(); + } + + @Override + public URI getVariableRelativePathLocation(URI location) { + try { + URI result = convertToRelative(location, false, null); + if (!result.equals(location)) + return result; + } catch (CoreException e) { + // handled by returning null + } + return null; + } + + /** + * @see IPathVariableManager#convertToUserEditableFormat(String, boolean) + */ + @Override + public String convertToUserEditableFormat(String value, boolean locationFormat) { + return PathVariableUtil.convertToUserEditableFormatInternal(value, locationFormat); + } + + @Override + public String convertFromUserEditableFormat(String userFormat, boolean locationFormat) { + return PathVariableUtil.convertFromUserEditableFormatInternal(this, userFormat, locationFormat); + } + + @Override + public boolean isUserDefined(String name) { + return ProjectVariableProviderManager.getDefault().findDescriptor(name) == null; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java new file mode 100644 index 0000000000..013dc3ee44 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PathVariableUtil.java @@ -0,0 +1,504 @@ +/******************************************************************************* + * Copyright (c) 2008, 2016 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.util.ArrayList; +import java.util.LinkedList; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.resources.projectvariables.*; +import org.eclipse.core.resources.IPathVariableManager; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.*; + +public class PathVariableUtil { + + static public String getUniqueVariableName(String variable, IResource resource) { + int index = 1; + variable = getValidVariableName(variable); + String destVariable = variable; + + IPathVariableManager pathVariableManager = resource.getPathVariableManager(); + if (destVariable.startsWith(ParentVariableResolver.NAME) || destVariable.startsWith(ProjectLocationVariableResolver.NAME)) + destVariable = "copy_" + destVariable; //$NON-NLS-1$ + + while (pathVariableManager.isDefined(destVariable)) { + destVariable = destVariable + index; + index++; + } + return destVariable; + } + + public static String getValidVariableName(String variable) { + // remove the argument part if the variable is of the form ${VAR-ARG} + int argumentIndex = variable.indexOf('-'); + if (argumentIndex != -1) + variable = variable.substring(0, argumentIndex); + + variable = variable.trim(); + char first = variable.charAt(0); + if (!Character.isLetter(first) && first != '_') { + variable = 'A' + variable; + } + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < variable.length(); i++) { + char c = variable.charAt(i); + if ((Character.isLetter(c) || Character.isDigit(c) || c == '_') && !Character.isWhitespace(c)) + builder.append(c); + } + variable = builder.toString(); + return variable; + } + + public static IPath convertToPathRelativeMacro(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint) throws CoreException { + return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, true, true); + } + + static public IPath convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint) throws CoreException { + return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, true, false); + } + + static public URI convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint) throws CoreException { + return URIUtil.toURI(convertToRelative(pathVariableManager, URIUtil.toPath(originalPath), resource, force, variableHint, true, false)); + } + + static public URI convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro) throws CoreException { + return URIUtil.toURI(convertToRelative(pathVariableManager, URIUtil.toPath(originalPath), resource, force, variableHint)); + } + + static private IPath convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro) throws CoreException { + if (variableHint != null && pathVariableManager.isDefined(variableHint)) { + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variableHint)); + if (value != null) + return wrapInProperFormat(makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variableHint, generateMacro), generateMacro); + } + IPath path = convertToProperCase(originalPath); + IPath newPath = null; + int maxMatchLength = -1; + String[] existingVariables = pathVariableManager.getPathVariableNames(); + for (int i = 0; i < existingVariables.length; i++) { + String variable = existingVariables[i]; + if (skipWorkspace) { + // Variables relative to the workspace are not portable, and defeat the purpose of having linked resource locations, + // so they should not automatically be created relative to the workspace. + if (variable.equals(WorkspaceLocationVariableResolver.NAME)) + continue; + } + if (variable.equals(WorkspaceParentLocationVariableResolver.NAME)) + continue; + if (variable.equals(ParentVariableResolver.NAME)) + continue; + // find closest path to the original path + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); + if (value != null) { + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + if (value.isPrefixOf(path)) { + int matchLength = value.segmentCount(); + if (matchLength > maxMatchLength) { + maxMatchLength = matchLength; + newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); + } + } + } + } + if (newPath != null) + return wrapInProperFormat(newPath, generateMacro); + + if (force) { + int originalSegmentCount = originalPath.segmentCount(); + for (int j = 0; j <= originalSegmentCount; j++) { + IPath matchingPath = path.removeLastSegments(j); + int minDifference = Integer.MAX_VALUE; + for (int k = 0; k < existingVariables.length; k++) { + String variable = existingVariables[k]; + if (skipWorkspace) { + if (variable.equals(WorkspaceLocationVariableResolver.NAME)) + continue; + } + if (variable.equals(WorkspaceParentLocationVariableResolver.NAME)) + continue; + if (variable.equals(ParentVariableResolver.NAME)) + continue; + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); + if (value != null) { + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + if (matchingPath.isPrefixOf(value)) { + int difference = value.segmentCount() - originalSegmentCount; + if (difference < minDifference) { + minDifference = difference; + newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); + } + } + } + } + if (newPath != null) + return wrapInProperFormat(newPath, generateMacro); + } + if (originalSegmentCount == 0) { + String variable = ProjectLocationVariableResolver.NAME; + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + if (originalPath.isPrefixOf(value)) + newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); + if (newPath != null) + return wrapInProperFormat(newPath, generateMacro); + } + } + + if (skipWorkspace) + return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, false, generateMacro); + return originalPath; + } + + private static IPath wrapInProperFormat(IPath newPath, boolean generateMacro) { + if (generateMacro) + newPath = PathVariableUtil.buildVariableMacro(newPath); + return newPath; + } + + private static IPath makeRelativeToVariable(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean generateMacro) { + IPath path = convertToProperCase(originalPath); + IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variableHint)); + value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); + int valueSegmentCount = value.segmentCount(); + if (value.isPrefixOf(path)) { + // transform "c:/foo/bar" into "FOO/bar" + IPath tmp = Path.fromOSString(variableHint); + for (int j = valueSegmentCount; j < originalPath.segmentCount(); j++) { + tmp = tmp.append(originalPath.segment(j)); + } + return tmp; + } + + if (force) { + if (devicesAreCompatible(path, value)) { + // transform "c:/foo/bar/other_child/file.txt" into "${PARENT-1-BAR_CHILD}/other_child/file.txt" + int matchingFirstSegments = path.matchingFirstSegments(value); + if (matchingFirstSegments >= 0) { + String originalName = buildParentPathVariable(variableHint, valueSegmentCount - matchingFirstSegments, true); + IPath tmp = Path.fromOSString(originalName); + for (int j = matchingFirstSegments; j < originalPath.segmentCount(); j++) { + tmp = tmp.append(originalPath.segment(j)); + } + return tmp; + } + } + } + return originalPath; + } + + private static boolean devicesAreCompatible(IPath path, IPath value) { + return (path.getDevice() != null && value.getDevice() != null) ? (path.getDevice().equals(value.getDevice())) : (path.getDevice() == value.getDevice()); + } + + static private IPath convertToProperCase(IPath path) { + if (Platform.getOS().equals(Platform.OS_WIN32)) + return Path.fromPortableString(path.toPortableString().toLowerCase()); + return path; + } + + static public boolean isParentVariable(String variableString) { + return variableString.startsWith(ParentVariableResolver.NAME + '-'); + } + + // the format is PARENT-COUNT-ARGUMENT + static public int getParentVariableCount(String variableString) { + String items[] = variableString.split("-"); //$NON-NLS-1$ + if (items.length == 3) { + try { + Integer count = Integer.valueOf(items[1]); + return count.intValue(); + } catch (NumberFormatException e) { + // nothing + } + } + return -1; + } + + // the format is PARENT-COUNT-ARGUMENT + static public String getParentVariableArgument(String variableString) { + String items[] = variableString.split("-"); //$NON-NLS-1$ + if (items.length == 3) + return items[2]; + return null; + } + + static public String buildParentPathVariable(String variable, int difference, boolean generateMacro) { + String newString = ParentVariableResolver.NAME + "-" + difference + "-" + variable; //$NON-NLS-1$//$NON-NLS-2$ + + if (!generateMacro) + newString = "${" + newString + "}"; //$NON-NLS-1$//$NON-NLS-2$ + return newString; + } + + public static IPath buildVariableMacro(IPath relativeSrcValue) { + String variable = relativeSrcValue.segment(0); + variable = "${" + variable + "}"; //$NON-NLS-1$//$NON-NLS-2$ + return Path.fromOSString(variable).append(relativeSrcValue.removeFirstSegments(1)); + } + + public static String convertFromUserEditableFormatInternal(IPathVariableManager manager, String userFormat, boolean locationFormat) { + char pathPrefix = 0; + if ((userFormat.length() > 0) && (userFormat.charAt(0) == '/' || userFormat.charAt(0) == '\\')) + pathPrefix = userFormat.charAt(0); + String components[] = splitPathComponents(userFormat); + for (int i = 0; i < components.length; i++) { + if (components[i] == null) + continue; + if (isDotDot(components[i])) { + int parentCount = 1; + components[i] = null; + for (int j = i + 1; j < components.length; j++) { + if (components[j] != null) { + if (isDotDot(components[j])) { + parentCount++; + components[j] = null; + } else + break; + } + } + if (i == 0) // this means the value is implicitly relative to the project location + components[0] = PathVariableUtil.buildParentPathVariable(ProjectLocationVariableResolver.NAME, parentCount, false); + else { + for (int j = i - 1; j >= 0; j--) { + if (parentCount == 0) + break; + if (components[j] == null) + continue; + String variable = extractVariable(components[j]); + + boolean hasVariableWithMacroSyntax = true; + if (variable.length() == 0 && (locationFormat && j == 0)) { + variable = components[j]; + hasVariableWithMacroSyntax = false; + } + + try { + if (variable.length() > 0) { + String prefix = ""; //$NON-NLS-1$ + if (hasVariableWithMacroSyntax) { + int indexOfVariable = components[j].indexOf(variable) - "${".length(); //$NON-NLS-1$ + prefix = components[j].substring(0, indexOfVariable); + String suffix = components[j].substring(indexOfVariable + "${".length() + variable.length() + "}".length()); //$NON-NLS-1$ //$NON-NLS-2$ + if (suffix.length() != 0) { + // Create an intermediate variable, since a syntax of "${VAR}foo/../" + // can't be converted to a "${PARENT-1-VAR}foo" variable. + // So instead, an intermediate variable "VARFOO" will be created of value + // "${VAR}foo", and the string "${PARENT-1-VARFOO}" will be inserted. + String intermediateVariable = PathVariableUtil.getValidVariableName(variable + suffix); + IPath intermediateValue = Path.fromPortableString(components[j]); + int intermediateVariableIndex = 1; + String originalIntermediateVariableName = intermediateVariable; + while (manager.isDefined(intermediateVariable)) { + IPath tmpValue = URIUtil.toPath(manager.getURIValue(intermediateVariable)); + if (tmpValue.equals(intermediateValue)) + break; + intermediateVariable = originalIntermediateVariableName + intermediateVariableIndex; + } + if (!manager.isDefined(intermediateVariable)) + manager.setURIValue(intermediateVariable, URIUtil.toURI(intermediateValue)); + variable = intermediateVariable; + prefix = ""; //$NON-NLS-1$ + } + } + String newVariable = variable; + if (PathVariableUtil.isParentVariable(variable)) { + String argument = PathVariableUtil.getParentVariableArgument(variable); + int count = PathVariableUtil.getParentVariableCount(variable); + if (argument != null && count != -1) + newVariable = PathVariableUtil.buildParentPathVariable(argument, count + parentCount, locationFormat); + else + newVariable = PathVariableUtil.buildParentPathVariable(variable, parentCount, locationFormat); + } else + newVariable = PathVariableUtil.buildParentPathVariable(variable, parentCount, locationFormat); + components[j] = prefix + newVariable; + break; + } + components[j] = null; + parentCount--; + } catch (CoreException e) { + components[j] = null; + parentCount--; + } + } + } + } + } + StringBuilder buffer = new StringBuilder(); + if (pathPrefix != 0) + buffer.append(pathPrefix); + for (int i = 0; i < components.length; i++) { + if (components[i] != null) { + if (i > 0) + buffer.append(java.io.File.separator); + buffer.append(components[i]); + } + } + return buffer.toString(); + } + + private static boolean isDotDot(String component) { + return component.equals(".."); //$NON-NLS-1$ + } + + private static String[] splitPathComponents(String userFormat) { + ArrayList list = new ArrayList<>(); + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < userFormat.length(); i++) { + char c = userFormat.charAt(i); + if (c == '/' || c == '\\') { + if (buffer.length() > 0) + list.add(buffer.toString()); + buffer = new StringBuilder(); + } else + buffer.append(c); + } + if (buffer.length() > 0) + list.add(buffer.toString()); + return list.toArray(new String[0]); + } + + public static String convertToUserEditableFormatInternal(String value, boolean locationFormat) { + StringBuilder buffer = new StringBuilder(); + if (locationFormat) { + IPath path = Path.fromOSString(value); + if (path.isAbsolute()) + return path.toOSString(); + int index = value.indexOf(java.io.File.separator); + String variable = index != -1 ? value.substring(0, index) : value; + convertVariableToUserFormat(buffer, variable, variable, false); + if (index != -1) + buffer.append(value.substring(index)); + } else { + String components[] = splitVariablesAndContent(value); + for (int i = 0; i < components.length; i++) { + String variable = extractVariable(components[i]); + convertVariableToUserFormat(buffer, components[i], variable, true); + } + } + return buffer.toString(); + } + + private static void convertVariableToUserFormat(StringBuilder buffer, String component, String variable, boolean generateMacro) { + if (PathVariableUtil.isParentVariable(variable)) { + String argument = PathVariableUtil.getParentVariableArgument(variable); + int count = PathVariableUtil.getParentVariableCount(variable); + if (argument != null && count != -1) { + buffer.append(generateMacro ? PathVariableUtil.buildVariableMacro(Path.fromOSString(argument)) : Path.fromOSString(argument)); + for (int j = 0; j < count; j++) { + buffer.append(java.io.File.separator + ".."); //$NON-NLS-1$ + } + } else + buffer.append(component); + } else + buffer.append(component); + } + + /* + * Splits a value (returned by this.getValue(variable) in an array of + * string, where the array is divided between the value content and the + * value variables. + * + * For example, if the value is "${ECLIPSE_HOME}/plugins", the value + * returned will be {"${ECLIPSE_HOME}" "/plugins"} + */ + static String[] splitVariablesAndContent(String value) { + LinkedList result = new LinkedList<>(); + while (true) { + // we check if the value contains referenced variables with ${VAR} + int index = value.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = getMatchingBrace(value, index); + if (index > 0) + result.add(value.substring(0, index)); + result.add(value.substring(index, endIndex + 1)); + value = value.substring(endIndex + 1); + } else + break; + } + if (value.length() > 0) + result.add(value); + return result.toArray(new String[0]); + } + + /* + * Splits a value (returned by this.getValue(variable) in an array of + * string of the variables contained in the value. + * + * For example, if the value is "${ECLIPSE_HOME}/plugins", the value + * returned will be {"ECLIPSE_HOME"}. If the value is + * "${ECLIPSE_HOME}/${FOO}/plugins", the value returned will be + * {"ECLIPSE_HOME", "FOO"}. + */ + static String[] splitVariableNames(String value) { + LinkedList result = new LinkedList<>(); + while (true) { + int index = value.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = getMatchingBrace(value, index); + result.add(value.substring(index + 2, endIndex)); + value = value.substring(endIndex + 1); + } else + break; + } + return result.toArray(new String[0]); + } + + /* + * Extracts the variable name from a variable segment. + * + * For example, if the value is "${ECLIPSE_HOME}", the value returned will + * be "ECLIPSE_HOME". If the segment doesn't contain any variable, the value + * returned will be "". + */ + static String extractVariable(String segment) { + int index = segment.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = getMatchingBrace(segment, index); + return segment.substring(index + 2, endIndex); + } + return ""; //$NON-NLS-1$ + } + + // getMatchingBrace("${FOO}/something") returns 5 + // getMatchingBrace("${${OTHER}}/something") returns 10 + // getMatchingBrace("${FOO") returns 5 + static int getMatchingBrace(String value, int index) { + int scope = 0; + for (int i = index + 1; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '}') { + if (scope == 0) + return i; + scope--; + } + if (c == '$') { + if ((i + 1 < value.length()) && (value.charAt(i + 1) == '{')) + scope++; + } + } + return value.length(); + } + + /** + * Returns whether this variable is suited for programmatically determining + * which variable is the most appropriate when creating new linked resources. + * + * @return true if the path variable is preferred. + */ + static public boolean isPreferred(String variableName) { + return !(variableName.equals(WorkspaceLocationVariableResolver.NAME) || variableName.equals(WorkspaceParentLocationVariableResolver.NAME) || variableName.equals(ParentVariableResolver.NAME)); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java new file mode 100644 index 0000000000..b8fa8f03f0 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * 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 java.io.IOException; +import java.net.*; +import org.eclipse.core.internal.boot.PlatformURLConnection; +import org.eclipse.core.internal.boot.PlatformURLHandler; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.osgi.util.NLS; + +/** + * Platform URL support + * platform:/resource// maps to resource in current workspace + */ +public class PlatformURLResourceConnection extends PlatformURLConnection { + + // resource/ protocol + public static final String RESOURCE = "resource"; //$NON-NLS-1$ + public static final String RESOURCE_URL_STRING = PlatformURLHandler.PROTOCOL + PlatformURLHandler.PROTOCOL_SEPARATOR + '/' + RESOURCE + '/'; + private static URL rootURL; + + public PlatformURLResourceConnection(URL url) { + super(url); + } + + @Override + protected boolean allowCaching() { + return false; // don't cache, workspace is local + } + + @Override + protected URL resolve() throws IOException { + String filePath = url.getFile().trim(); + filePath = URLDecoder.decode(filePath, "UTF-8"); //$NON-NLS-1$ + IPath spec = new Path(filePath).makeRelative(); + if (!spec.segment(0).equals(RESOURCE)) + throw new IOException(NLS.bind(Messages.url_badVariant, url)); + int count = spec.segmentCount(); + // if there is only one segment then we are talking about the workspace root. + if (count == 1) + return rootURL; + // if there are two segments then the second is a project name. + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(spec.segment(1)); + if (!project.exists()) { + String message = NLS.bind(Messages.url_couldNotResolve_projectDoesNotExist, project.getName(), url.toExternalForm()); + throw new IOException(message); + } + + IResource resource = null; + IPath result = null; + + if (count == 2) { + resource = project; + } else { + spec = spec.removeFirstSegments(2); + resource = project.getFile(spec); + } + + result = resource.getLocation(); + + if (result == null) { + URI uri = resource.getLocationURI(); + if (uri != null) { + try { + URL url2 = uri.toURL(); + if (url2.getProtocol().equals("file")) //$NON-NLS-1$ + return url2; + } catch (MalformedURLException e) { + String message = NLS.bind(Messages.url_couldNotResolve_URLProtocolHandlerCanNotResolveURL, uri.toString(), url.toExternalForm()); + throw new IOException(message); + } + } + String message = NLS.bind(Messages.url_couldNotResolve_resourceLocationCanNotBeDetermined, resource.getFullPath(), url.toExternalForm()); + throw new IOException(message); + } + return new URL("file", "", result.toString()); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * This method is called during resource plugin startup() initialization. + * + * @param root URL to the root of the current workspace. + */ + public static void startup(IPath root) { + // register connection type for platform:/resource/ handling + if (rootURL != null) + return; + try { + rootURL = root.toFile().toURL(); + } catch (MalformedURLException e) { + // should never happen but if it does, the resource URL cannot be supported. + return; + } + PlatformURLHandler.register(RESOURCE, PlatformURLResourceConnection.class); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java new file mode 100644 index 0000000000..3ae12e17dc --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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 Corporation - initial API and implementation + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.preferences.*; + +/** + * @since 3.1 + */ +public class PreferenceInitializer extends AbstractPreferenceInitializer { + + // internal preference keys + public static final String PREF_OPERATIONS_PER_SNAPSHOT = "snapshots.operations"; //$NON-NLS-1$ + public static final String PREF_DELTA_EXPIRATION = "delta.expiration"; //$NON-NLS-1$ + + // DEFAULTS + public static final boolean PREF_AUTO_REFRESH_DEFAULT = false; + public static final boolean PREF_LIGHTWEIGHT_AUTO_REFRESH_DEFAULT = false; + public static final boolean PREF_DISABLE_LINKING_DEFAULT = false; + public static final String PREF_ENCODING_DEFAULT = ""; //$NON-NLS-1$ + public static final boolean PREF_AUTO_BUILDING_DEFAULT = true; + public static final String PREF_BUILD_ORDER_DEFAULT = ""; //$NON-NLS-1$ + public static final int PREF_MAX_BUILD_ITERATIONS_DEFAULT = 10; + public static final boolean PREF_DEFAULT_BUILD_ORDER_DEFAULT = true; + public final static long PREF_SNAPSHOT_INTERVAL_DEFAULT = 5 * 60 * 1000l; // 5 min + public static final int PREF_OPERATIONS_PER_SNAPSHOT_DEFAULT = 100; + public static final boolean PREF_APPLY_FILE_STATE_POLICY_DEFAULT = true; + public static final long PREF_FILE_STATE_LONGEVITY_DEFAULT = 7 * 24 * 3600 * 1000l; // 7 days + public static final long PREF_MAX_FILE_STATE_SIZE_DEFAULT = 1024 * 1024l; // 1 MB + public static final int PREF_MAX_FILE_STATES_DEFAULT = 50; + public static final long PREF_DELTA_EXPIRATION_DEFAULT = 30 * 24 * 3600 * 1000l; // 30 days + + public PreferenceInitializer() { + super(); + } + + @Override + public void initializeDefaultPreferences() { + IEclipsePreferences node = DefaultScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES); + // auto-refresh default + node.putBoolean(ResourcesPlugin.PREF_AUTO_REFRESH, PREF_AUTO_REFRESH_DEFAULT); + node.putBoolean(ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, PREF_LIGHTWEIGHT_AUTO_REFRESH_DEFAULT); + + // linked resources default + node.putBoolean(ResourcesPlugin.PREF_DISABLE_LINKING, PREF_DISABLE_LINKING_DEFAULT); + + // build manager defaults + node.putBoolean(ResourcesPlugin.PREF_AUTO_BUILDING, PREF_AUTO_BUILDING_DEFAULT); + node.put(ResourcesPlugin.PREF_BUILD_ORDER, PREF_BUILD_ORDER_DEFAULT); + node.putInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS, PREF_MAX_BUILD_ITERATIONS_DEFAULT); + node.putBoolean(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER, PREF_DEFAULT_BUILD_ORDER_DEFAULT); + + // history store defaults + node.putBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY, PREF_APPLY_FILE_STATE_POLICY_DEFAULT); + node.putLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, PREF_FILE_STATE_LONGEVITY_DEFAULT); + node.putLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, PREF_MAX_FILE_STATE_SIZE_DEFAULT); + node.putInt(ResourcesPlugin.PREF_MAX_FILE_STATES, PREF_MAX_FILE_STATES_DEFAULT); + + // save manager defaults + node.putLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL, PREF_SNAPSHOT_INTERVAL_DEFAULT); + node.putInt(PREF_OPERATIONS_PER_SNAPSHOT, PREF_OPERATIONS_PER_SNAPSHOT_DEFAULT); + node.putLong(PREF_DELTA_EXPIRATION, PREF_DELTA_EXPIRATION_DEFAULT); + + // encoding defaults + node.put(ResourcesPlugin.PREF_ENCODING, PREF_ENCODING_DEFAULT); + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java new file mode 100644 index 0000000000..cfe6f7a62e --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java @@ -0,0 +1,1385 @@ +/******************************************************************************* + * 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] Project Path Variable Support + * Anton Leherbauer (Wind River) - [198591] Allow Builder to specify scheduling rule + * Francis Lynch (Wind River) - [301563] Save and load tree snapshots + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * Broadcom Corporation - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.internal.events.LifecycleEvent; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IMoveDeleteHook; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentTypeMatcher; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.osgi.util.NLS; + +public class Project extends Container implements IProject { + /** + * Option constant (value 2) indicating that the snapshot location shall be + * persisted with the project for autoloading the snapshot when the project + * is imported in another workspace. + *

                  + * EXPERIMENTAL. This constant has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. Please do not use this API without + * consulting with the Platform Core team. + *

                  + * @see #saveSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public static final int SNAPSHOT_SET_AUTOLOAD = 2; + + protected Project(IPath path, Workspace container) { + super(path, container); + } + + protected void assertCreateRequirements(IProjectDescription description) throws CoreException { + checkDoesNotExist(); + checkDescription(this, description, false); + URI location = description.getLocationURI(); + if (location != null) + return; + //if the project is in the default location, need to check for collision with existing folder of different case + if (!Workspace.caseSensitive) { + IFileStore store = getStore(); + IFileInfo localInfo = store.fetchInfo(); + if (localInfo.exists()) { + 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); + } + } + } + } + + /* + * If the creation boolean is true then this method is being called on project creation. + * Otherwise it is being called via #setDescription. The difference is that we don't allow + * some description fields to change value after project creation. (e.g. project location) + */ + protected MultiStatus basicSetDescription(ProjectDescription description, int updateFlags) { + String message = Messages.resources_projectDesc; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_WRITE_METADATA, message, null); + ProjectDescription current = internalGetDescription(); + current.setComment(description.getComment()); + current.setSnapshotLocationURI(description.getSnapshotLocationURI()); + + // set the build order before setting the references or the natures + current.setBuildSpec(description.getBuildSpec(true)); + + // set the references before the natures + boolean flushOrder = false; + IProject[] oldReferences = current.getReferencedProjects(); + IProject[] newReferences = description.getReferencedProjects(); + if (!Arrays.equals(oldReferences, newReferences)) { + current.setReferencedProjects(newReferences); + flushOrder = true; + } + // Update the dynamic state + flushOrder |= current.updateDynamicState(description); + + if (flushOrder) + workspace.flushBuildOrder(); + + // the natures last as this may cause recursive calls to setDescription. + if ((updateFlags & IResource.AVOID_NATURE_CONFIG) == 0) + workspace.getNatureManager().configureNatures(this, current, description, result); + else + current.setNatureIds(description.getNatureIds(false)); + return result; + } + + @Override + public void build(int trigger, IProgressMonitor monitor) throws CoreException { + if (!isAccessible()) + return; + internalBuild(getActiveBuildConfig(), trigger, null, null, monitor); + } + + @Override + public void build(int trigger, String builderName, Map args, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(builderName); + if (!isAccessible()) + return; + internalBuild(getActiveBuildConfig(), trigger, builderName, args, monitor); + } + + @Override + public void build(IBuildConfiguration config, int trigger, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(config); + // If project isn't accessible, or doesn't contain the build configuration, nothing to do. + if (!isAccessible() || !hasBuildConfig(config.getName())) + return; + internalBuild(config, trigger, null, null, monitor); + } + + /** + * Checks that this resource is accessible. Typically this means that it + * exists. In the case of projects, they must also be open. + * If phantom is true, phantom resources are considered. + * + * @exception CoreException if this resource is not accessible + */ + @Override + public void checkAccessible(int flags) throws CoreException { + super.checkAccessible(flags); + if (!isOpen(flags)) { + String message = NLS.bind(Messages.resources_mustBeOpen, getName()); + throw new ResourceException(IResourceStatus.PROJECT_NOT_OPEN, getFullPath(), message, null); + } + } + + /** + * Checks validity of the given project description. + */ + protected void checkDescription(IProject project, IProjectDescription desc, boolean moving) throws CoreException { + URI location = desc.getLocationURI(); + String message = Messages.resources_invalidProjDesc; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_VALUE, message, null); + status.merge(workspace.validateName(desc.getName(), IResource.PROJECT)); + if (moving) { + // if we got here from a move call then we should check the location in the description since + // its possible that we want to do a rename without moving the contents. (and we shouldn't + // throw an Overlapping mapping exception in this case) So if the source description's location + // is null (we are using the default) or if the locations aren't equal, then validate the location + // of the new description. Otherwise both locations aren't null and they are equal so ignore validation. + URI sourceLocation = internalGetDescription().getLocationURI(); + if (sourceLocation == null || !sourceLocation.equals(location)) + status.merge(workspace.validateProjectLocationURI(project, location)); + } else + // otherwise continue on like before + status.merge(workspace.validateProjectLocationURI(project, location)); + if (!status.isOK()) + throw new ResourceException(status); + } + + @Override + public void close(IProgressMonitor monitor) throws CoreException { + String msg = NLS.bind(Messages.resources_closing_1, getName()); + SubMonitor subMonitor = SubMonitor.convert(monitor, msg, 100); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, subMonitor.newChild(1)); + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkExists(flags, true); + subMonitor.subTask(msg); + if (!isOpen(flags)) + return; + // Signal that this resource is about to be closed. Do this at the very + // beginning so that infrastructure pieces have a chance to do clean up + // while the resources still exist. + workspace.beginOperation(true); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, this)); + // flush the build order early in case there is a problem + workspace.flushBuildOrder(); + IProgressMonitor sub = subMonitor.newChild(49, SubMonitor.SUPPRESS_SUBTASK); + IStatus saveStatus = workspace.getSaveManager().save(ISaveContext.PROJECT_SAVE, this, sub); + internalClose(subMonitor.newChild(49)); + if (saveStatus != null && !saveStatus.isOK()) + throw new ResourceException(saveStatus); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true); + } + } + + @Override + public void copy(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + // FIXME - the logic here for copying projects needs to be moved to Resource.copy + // so that IResource.copy(IPath,int,IProgressMonitor) works properly for + // projects and honours all update flags + monitor = Policy.monitorFor(monitor); + if (destination.segmentCount() == 1) { + // copy project to project + String projectName = destination.segment(0); + IProjectDescription desc = getDescription(); + desc.setName(projectName); + desc.setLocation(null); + ((ProjectDescription) desc).setSnapshotLocationURI(null); + internalCopy(desc, updateFlags, monitor); + } else { + // will fail since we're trying to copy a project to a non-project + checkCopyRequirements(destination, IResource.PROJECT, updateFlags); + } + } + + @Override + public void copy(IProjectDescription destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + // FIXME - the logic here for copying projects needs to be moved to Resource.copy + // so that IResource.copy(IProjectDescription,int,IProgressMonitor) works properly for + // projects and honours all update flags + Assert.isNotNull(destination); + internalCopy(destination, updateFlags, monitor); + } + + protected void copyMetaArea(IProject source, IProject destination, IProgressMonitor monitor) throws CoreException { + IFileStore oldMetaArea = EFS.getFileSystem(EFS.SCHEME_FILE).getStore(workspace.getMetaArea().locationFor(source)); + IFileStore newMetaArea = EFS.getFileSystem(EFS.SCHEME_FILE).getStore(workspace.getMetaArea().locationFor(destination)); + oldMetaArea.copy(newMetaArea, EFS.NONE, monitor); + } + + @Override + public void create(IProgressMonitor monitor) throws CoreException { + create(null, monitor); + } + + @Override + public void create(IProjectDescription description, IProgressMonitor monitor) throws CoreException { + create(description, IResource.NONE, monitor); + } + + @Override + public void create(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask(Messages.resources_create, Policy.totalWork); + checkValidPath(path, PROJECT, false); + final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); + try { + workspace.prepareOperation(rule, monitor); + if (description == null) { + description = new ProjectDescription(); + description.setName(getName()); + } + assertCreateRequirements(description); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CREATE, this)); + workspace.beginOperation(true); + workspace.createResource(this, updateFlags); + workspace.getMetaArea().create(this); + ProjectInfo info = (ProjectInfo) getResourceInfo(false, true); + + // setup description to obtain project location + ProjectDescription desc = (ProjectDescription) ((ProjectDescription) description).clone(); + desc.setLocationURI(FileUtil.canonicalURI(description.getLocationURI())); + desc.setName(getName()); + internalSetDescription(desc, false); + // see if there potentially are already contents on disk + final boolean hasSavedDescription = getLocalManager().hasSavedDescription(this); + boolean hasContent = hasSavedDescription; + //if there is no project description, there might still be content on disk + if (!hasSavedDescription) + hasContent = getLocalManager().hasSavedContent(this); + try { + // look for a description on disk + if (hasSavedDescription) { + updateDescription(); + //make sure the .location file is written + workspace.getMetaArea().writePrivateDescription(this); + } else { + //write out the project + writeDescription(IResource.FORCE); + } + } catch (CoreException e) { + workspace.deleteResource(this); + throw e; + } + // inaccessible projects have a null modification stamp. + // set this after setting the description as #setDescription + // updates the stamp + info.clearModificationStamp(); + //if a project already had content on disk, mark the project as having unknown children + if (hasContent) + info.set(ICoreConstants.M_CHILDREN_UNKNOWN); + workspace.getSaveManager().requestSnapshot(); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true); + } + } finally { + monitor.done(); + } + } + + @Override + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT; + delete(updateFlags, monitor); + } + + @Override + public void delete(boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + delete(updateFlags, monitor); + } + + @Override + public void deleteResource(boolean convertToPhantom, MultiStatus status) throws CoreException { + super.deleteResource(convertToPhantom, status); + // Clear the history store. + clearHistory(null); + // Delete the project metadata. + workspace.getMetaArea().delete(this); + } + + @Override + protected void fixupAfterMoveSource() throws CoreException { + workspace.deleteResource(this); + // check if we deleted a preferences file + ProjectPreferences.deleted(this); + } + + @Override + public IBuildConfiguration getActiveBuildConfig() throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + return internalGetActiveBuildConfig(); + } + + @Override + public IBuildConfiguration getBuildConfig(String configName) throws CoreException { + if (configName == null) + return getActiveBuildConfig(); + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + IBuildConfiguration[] configs = internalGetBuildConfigs(false); + for (int i = 0; i < configs.length; i++) { + if (configs[i].getName().equals(configName)) { + return configs[i]; + } + } + throw new ResourceException(IResourceStatus.BUILD_CONFIGURATION_NOT_FOUND, getFullPath(), null, null); + } + + @Override + public IBuildConfiguration[] getBuildConfigs() throws CoreException { + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + return internalGetBuildConfigs(true); + } + + @Override + public IContentTypeMatcher getContentTypeMatcher() throws CoreException { + return workspace.getContentDescriptionManager().getContentTypeMatcher(this); + } + + @Override + public String getDefaultCharset(boolean checkImplicit) { + // non-existing resources default to parent's charset + if (!exists()) + return checkImplicit ? ResourcesPlugin.getEncoding() : null; + return workspace.getCharsetManager().getCharsetFor(getFullPath(), checkImplicit); + } + + @Override + public IProjectDescription getDescription() throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + ProjectDescription description = ((ProjectInfo) info).getDescription(); + //if the project is currently in the middle of being created, the description might not be available yet + if (description == null) + checkAccessible(NULL_FLAG); + return (IProjectDescription) description.clone(); + } + + @Override + public IProjectNature getNature(String natureID) throws CoreException { + // Has it already been initialized? + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + IProjectNature nature = info.getNature(natureID); + if (nature == null) { + // Not initialized yet. Does this project have the nature? + if (!hasNature(natureID)) + return null; + nature = workspace.getNatureManager().createNature(this, natureID); + info.setNature(natureID, nature); + } + return nature; + } + + @Override + public IContainer getParent() { + return workspace.getRoot(); + } + + @Override + @Deprecated + public IPath getPluginWorkingLocation(IPluginDescriptor plugin) { + if (plugin == null) + return null; + return getWorkingLocation(plugin.getUniqueIdentifier()); + } + + @Override + public IProject getProject() { + return this; + } + + @Override + public IPath getProjectRelativePath() { + return Path.EMPTY; + } + + @Override + public IPath getRawLocation() { + ProjectDescription description = internalGetDescription(); + return description == null ? null : description.getLocation(); + } + + @Override + public URI getRawLocationURI() { + ProjectDescription description = internalGetDescription(); + return description == null ? null : description.getLocationURI(); + } + + @Override + public IBuildConfiguration[] getReferencedBuildConfigs(String configName, boolean includeMissing) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + ProjectDescription description = ((ProjectInfo) info).getDescription(); + //if the project is currently in the middle of being created, the description might not be available yet + if (description == null) + checkAccessible(NULL_FLAG); + if (!hasBuildConfig(configName)) + throw new ResourceException(IResourceStatus.BUILD_CONFIGURATION_NOT_FOUND, getFullPath(), null, null); + return internalGetReferencedBuildConfigs(configName, includeMissing); + } + + @Override + public IProject[] getReferencedProjects() throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + ProjectDescription description = ((ProjectInfo) info).getDescription(); + //if the project is currently in the middle of being created, the description might not be available yet + if (description == null) + checkAccessible(NULL_FLAG); + return description.getAllReferences(true); + } + + @Override + public void clearCachedDynamicReferences() { + ResourceInfo info = getResourceInfo(false, false); + if (info == null) { + // If the project is not open there is no cached state and so there is nothing to do. + return; + } + ProjectDescription description = ((ProjectInfo) info).getDescription(); + if (description == null) { + // If the project is in the process of being created there is no cached state and nothing to do. + return; + } + description.clearCachedDynamicReferences(null); + } + + @Override + public IProject[] getReferencingProjects() { + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List result = new ArrayList<>(projects.length); + for (int i = 0; i < projects.length; i++) { + Project project = (Project) projects[i]; + if (!project.isAccessible()) + continue; + ProjectDescription description = project.internalGetDescription(); + if (description == null) + continue; + IProject[] references = description.getAllReferences(false); + for (int j = 0; j < references.length; j++) + if (references[j].equals(this)) { + result.add(projects[i]); + break; + } + } + return result.toArray(new IProject[result.size()]); + } + + @Override + public int getType() { + return PROJECT; + } + + @Override + public IPath getWorkingLocation(String id) { + if (id == null || !exists()) + return null; + IPath result = workspace.getMetaArea().getWorkingLocation(this, id); + result.toFile().mkdirs(); + return result; + } + + @Override + public boolean hasBuildConfig(String configName) throws CoreException { + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + checkAccessible(getFlags(info)); + return internalHasBuildConfig(configName); + } + + @Override + public boolean hasNature(String natureID) throws CoreException { + checkAccessible(getFlags(getResourceInfo(false, false))); + // use #internal method to avoid copy but still throw an + // exception if the resource doesn't exist. + IProjectDescription desc = internalGetDescription(); + if (desc == null) + checkAccessible(NULL_FLAG); + return desc.hasNature(natureID); + } + + /** + * Implements all build methods on IProject. + */ + protected void internalBuild(final IBuildConfiguration config, final int trigger, final String builderName, final Map args, IProgressMonitor monitor) throws CoreException { + workspace.run(new ICoreRunnable() { + @Override + public void run(IProgressMonitor innerMonitor) throws CoreException { + innerMonitor = Policy.monitorFor(innerMonitor); + final ISchedulingRule rule = workspace.getRoot(); + try { + innerMonitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + try { + workspace.prepareOperation(rule, innerMonitor); + if (!shouldBuild()) + return; + workspace.beginOperation(true); + workspace.aboutToBuild(Project.this, trigger); + } finally { + workspace.endOperation(rule, false); + } + final ISchedulingRule buildRule = workspace.getBuildManager().getRule(config, trigger, builderName, args); + try { + IStatus result; + workspace.prepareOperation(buildRule, innerMonitor); + //don't open the tree eagerly because it will be wasted if no build occurs + workspace.beginOperation(false); + result = workspace.getBuildManager().build(config, trigger, builderName, args, Policy.subMonitorFor(innerMonitor, Policy.opWork)); + if (!result.isOK()) + throw new ResourceException(result); + } finally { + workspace.endOperation(buildRule, false); + try { + workspace.prepareOperation(rule, innerMonitor); + //don't open the tree eagerly because it will be wasted if no change occurs + workspace.beginOperation(false); + workspace.broadcastBuildEvent(Project.this, IResourceChangeEvent.POST_BUILD, trigger); + //building may close the tree, so open it + if (workspace.getElementTree().isImmutable()) + workspace.newWorkingTree(); + } finally { + workspace.endOperation(rule, false); + } + } + } finally { + innerMonitor.done(); + } + } + + /** + * Returns whether this project should be built for a given trigger. + * @return true if the build should proceed, and false otherwise. + */ + private boolean shouldBuild() { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (!exists(flags, true) || !isOpen(flags)) + return false; + return true; + } + + }, null, IWorkspace.AVOID_UPDATE, monitor); + } + + /** + * Closes the project. This is called during restore when there is a failure + * to read the project description. Since it is called during workspace restore, + * it cannot start any operations. + */ + protected void internalClose(IProgressMonitor monitor) throws CoreException { + SubMonitor subMonitor = SubMonitor.convert(monitor, 2); + workspace.flushBuildOrder(); + getMarkerManager().removeMarkers(this, IResource.DEPTH_INFINITE); + subMonitor.worked(1); + // remove each member from the resource tree. + // DO NOT use resource.delete() as this will delete it from disk as well. + IResource[] members = members(IContainer.INCLUDE_PHANTOMS | IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + subMonitor.setWorkRemaining(members.length); + for (int i = 0; i < members.length; i++) { + Resource member = (Resource) members[i]; + workspace.deleteResource(member); + subMonitor.worked(1); + } + // finally mark the project as closed. + ResourceInfo info = getResourceInfo(false, true); + info.clear(M_OPEN); + info.clearSessionProperties(); + info.clearModificationStamp(); + info.setSyncInfo(null); + } + + protected void internalCopy(IProjectDescription destDesc, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_copying, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + String destName = destDesc.getName(); + IPath destPath = new Path(destName).makeAbsolute(); + Project destination = (Project) workspace.getRoot().getProject(destName); + final ISchedulingRule rule = workspace.getRuleFactory().copyRule(this, destination); + try { + workspace.prepareOperation(rule, monitor); + // The following assert method throws CoreExceptions as stated in the IProject.copy API + // and assert for programming errors. See checkCopyRequirements for more information. + assertCopyRequirements(destPath, IResource.PROJECT, updateFlags); + checkDescription(destination, destDesc, false); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_COPY, this, destination, updateFlags)); + + workspace.beginOperation(true); + getLocalManager().refresh(this, DEPTH_INFINITE, true, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + // close the property store so incorrect info is not copied to the destination + getPropertyManager().closePropertyStore(this); + getLocalManager().getHistoryStore().closeHistoryStore(this); + + // copy the meta area for the project + copyMetaArea(this, destination, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + + // copy just the project and not its children yet (tree node, properties) + internalCopyProjectOnly(destination, destDesc, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + + // set the description + destination.internalSetDescription(destDesc, false); + + //create the directory for the new project + destination.getStore().mkdir(EFS.NONE, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + + // call super.copy for each child (excluding project description file) + //make it a best effort copy + message = Messages.resources_copyProblem; + MultiStatus problems = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + + IResource[] children = members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + final int childCount = children.length; + final int childWork = childCount > 1 ? Policy.opWork * 50 / 100 / (childCount - 1) : 0; + for (int i = 0; i < childCount; i++) { + IResource child = children[i]; + if (!isProjectDescriptionFile(child)) { + try { + child.copy(destPath.append(child.getName()), updateFlags, Policy.subMonitorFor(monitor, childWork)); + } catch (CoreException e) { + problems.merge(e.getStatus()); + } + } + } + + // write out the new project description to the meta area + try { + destination.writeDescription(IResource.FORCE); + } catch (CoreException e) { + try { + destination.delete((updateFlags & IResource.FORCE) != 0, null); + } catch (CoreException e2) { + // ignore and rethrow the exception that got us here + } + throw e; + } + monitor.worked(Policy.opWork * 5 / 100); + + // refresh local + monitor.subTask(Messages.resources_updating); + getLocalManager().refresh(destination, DEPTH_INFINITE, true, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + if (!problems.isOK()) + throw new ResourceException(problems); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true); + } + } finally { + monitor.done(); + } + } + + /* + * Copies just the project and no children. Does NOT copy the meta area. + */ + protected void internalCopyProjectOnly(IResource destination, IProjectDescription destDesc, IProgressMonitor monitor) throws CoreException { + // close the property store so bogus values aren't copied to the destination + getPropertyManager().closePropertyStore(this); + getLocalManager().getHistoryStore().closeHistoryStore(this); + // copy the tree and properties + workspace.copyTree(this, destination.getFullPath(), IResource.DEPTH_ZERO, IResource.NONE, false); + getPropertyManager().copy(this, destination, IResource.DEPTH_ZERO); + + ProjectInfo info = (ProjectInfo) ((Resource) destination).getResourceInfo(false, true); + + //copy the hidden metadata that we store in the project description + ProjectDescription projectDesc = (ProjectDescription) destDesc; + ProjectDescription internalDesc = ((Project) destination.getProject()).internalGetDescription(); + projectDesc.setLinkDescriptions(internalDesc.getLinks()); + projectDesc.setFilterDescriptions(internalDesc.getFilters()); + projectDesc.setVariableDescriptions(internalDesc.getVariables()); + + //clear properties, markers, and description for the new project, because they shouldn't be copied. + info.description = null; + info.natures = null; + info.setMarkers(null); + info.clearSessionProperties(); + } + + /** + * Like {@link #getActiveBuildConfig()} but doesn't check accessibility. + * Project must be accessible. + * @see #getActiveBuildConfig() + */ + IBuildConfiguration internalGetActiveBuildConfig() { + String configName = internalGetDescription().activeConfiguration; + try { + if (configName != null) + return getBuildConfig(configName); + } catch (CoreException e) { + // Build configuration doesn't exist; treat the first as active. + } + return internalGetBuildConfigs(false)[0]; + } + + /** + * @return IBuildConfiguration[] always contains at least one build configuration + */ + public IBuildConfiguration[] internalGetBuildConfigs(boolean makeCopy) { + ProjectDescription desc = internalGetDescription(); + if (desc == null) + return new IBuildConfiguration[] {new BuildConfiguration(this, IBuildConfiguration.DEFAULT_CONFIG_NAME)}; + return desc.getBuildConfigs(this, makeCopy); + } + + /** + * This is an internal helper method. This implementation is different from the API + * method getDescription(). This one does not check the project accessibility. It exists + * in order to prevent "chicken and egg" problems in places like the project creation. + * It may return null. + */ + public ProjectDescription internalGetDescription() { + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + if (info == null) + return null; + return info.getDescription(); + } + + /** + * Returns the IBuildConfigurations referenced by the passed in build configuration + * @param configName to find references for + * @return IBuildConfiguration[] of referenced configurations; never null. + */ + public IBuildConfiguration[] internalGetReferencedBuildConfigs(String configName, boolean includeMissing) { + ProjectDescription description = internalGetDescription(); + IBuildConfiguration[] refs = description.getAllBuildConfigReferences(configName, false); + Collection configs = new LinkedHashSet<>(refs.length); + for (int i = 0; i < refs.length; i++) { + try { + configs.add((((BuildConfiguration) refs[i]).getBuildConfig())); + } catch (CoreException e) { + // The project isn't accessible, or the build configuration doesn't exist + // on the project. If requested return the full set of build references which may + // be useful to API consumers + if (includeMissing) + configs.add(refs[i]); + } + } + return configs.toArray(new IBuildConfiguration[configs.size()]); + } + + boolean internalHasBuildConfig(String configName) { + return internalGetDescription().hasBuildConfig(configName); + } + + /** + * Sets this project's description to the given value. This is the body of the + * corresponding API method but is needed separately since it is used + * during workspace restore (i.e., when you cannot do an operation) + */ + void internalSetDescription(IProjectDescription value, boolean incrementContentId) { + // Project has been added / removed. Build order is out-of-step + workspace.flushBuildOrder(); + + ProjectInfo info = (ProjectInfo) getResourceInfo(false, true); + info.setDescription((ProjectDescription) value); + getLocalManager().setLocation(this, info, value.getLocationURI()); + if (incrementContentId) { + info.incrementContentId(); + //if the project is not accessible, stamp will be null and should remain null + if (info.getModificationStamp() != NULL_STAMP) + workspace.updateModificationStamp(info); + } + } + + @Override + public void internalSetLocal(boolean flag, int depth) throws CoreException { + // do nothing for projects, but call for its children + if (depth == IResource.DEPTH_ZERO) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.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++) + ((Resource) children[i]).internalSetLocal(flag, depth); + } + + @Override + public boolean isAccessible() { + return isOpen(); + } + + @Override + public boolean isDerived(int options) { + //projects are never derived + return false; + } + + @Override + public boolean isLinked(int options) { + return false;//projects are never linked + } + + @Override + public boolean isVirtual() { + return false; // projects are never virtual + } + + @Override + public boolean isTeamPrivateMember(int options) { + return false;//projects are never team private members + } + + @Deprecated + @Override + public boolean isLocal(int depth) { + // the flags parameter is ignored for projects so pass anything + return isLocal(-1, depth); + } + + @Deprecated + @Override + public boolean isLocal(int flags, int depth) { + // don't check the flags....projects are always local + 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 boolean isNatureEnabled(String natureId) throws CoreException { + checkAccessible(getFlags(getResourceInfo(false, false))); + return workspace.getNatureManager().isNatureEnabled(this, natureId); + } + + @Override + public boolean isOpen() { + ResourceInfo info = getResourceInfo(false, false); + return isOpen(getFlags(info)); + } + + public boolean isOpen(int flags) { + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_OPEN); + } + + /** + * Returns true if this resource represents the project description file, and + * false otherwise. + */ + protected boolean isProjectDescriptionFile(IResource resource) { + return resource.getType() == IResource.FILE && resource.getFullPath().segmentCount() == 2 && resource.getName().equals(IProjectDescription.DESCRIPTION_FILE_NAME); + } + + @Override + public void loadSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + // load a snapshot of refresh information when project is not opened + if (isOpen()) { + String message = Messages.resources_projectMustNotBeOpen; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, null); + throw new CoreException(status); + } + internalLoadSnapshot(options, snapshotLocation, monitor); + } + + /** + * Loads a snapshot of project meta-data from the given location URI. + * Like {@link IProject#loadSnapshot(int, URI, IProgressMonitor)} but can be + * called when the project is open. + * + * @see IProject#saveSnapshot(int, URI, IProgressMonitor) + */ + private void internalLoadSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + if ((options & SNAPSHOT_TREE) != 0) { + // ensure that path variables are resolved: only ws accessible while project is closed + snapshotLocation = workspace.getPathVariableManager().resolveURI(snapshotLocation); + if (!snapshotLocation.isAbsolute()) { + String message = NLS.bind(Messages.projRead_badSnapshotLocation, snapshotLocation.toString()); + throw new CoreException(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, message, null)); + } + // copy the snapshot from the URI into the project metadata + IPath snapshotPath = workspace.getMetaArea().getRefreshLocationFor(this); + IFileStore snapshotFileStore = EFS.getStore(org.eclipse.core.filesystem.URIUtil.toURI(snapshotPath)); + EFS.getStore(snapshotLocation).copy(snapshotFileStore, EFS.OVERWRITE, monitor); + } + } + + @Override + public void move(IProjectDescription destination, boolean force, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(destination); + move(destination, force ? IResource.FORCE : IResource.NONE, monitor); + } + + @Override + public void move(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(description); + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_moving, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + IProject destination = workspace.getRoot().getProject(description.getName()); + final ISchedulingRule rule = workspace.getRuleFactory().moveRule(this, destination); + try { + workspace.prepareOperation(rule, monitor); + // The following assert method throws CoreExceptions as stated in the IResource.move API + // and assert for programming errors. See checkMoveRequirements for more information. + if (!getName().equals(description.getName())) { + IPath destPath = Path.ROOT.append(description.getName()); + assertMoveRequirements(destPath, IResource.PROJECT, updateFlags); + } + checkDescription(destination, description, true); + workspace.beginOperation(true); + message = Messages.resources_moveProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, null); + WorkManager workManager = workspace.getWorkManager(); + ResourceTree tree = new ResourceTree(getLocalManager(), workManager.getLock(), status, updateFlags); + IMoveDeleteHook hook = workspace.getMoveDeleteHook(); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_MOVE, this, destination, updateFlags)); + int depth = 0; + try { + depth = workManager.beginUnprotected(); + if (!hook.moveProject(tree, this, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2))) + tree.standardMoveProject(this, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2)); + } finally { + workManager.endUnprotected(depth); + } + // Invalidate the tree for further use by clients. + tree.makeInvalid(); + if (!tree.getStatus().isOK()) + throw new ResourceException(tree.getStatus()); + // make sure the move operation is remembered + workspace.getSaveManager().requestSnapshot(); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true); + } + } finally { + monitor.done(); + } + } + + @Override + public void open(int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String msg = NLS.bind(Messages.resources_opening_1, getName()); + monitor.beginTask(msg, Policy.totalWork); + monitor.subTask(msg); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, monitor); + ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); + int flags = getFlags(info); + checkExists(flags, true); + if (isOpen(flags)) + return; + + workspace.beginOperation(true); + // flush the build order early in case there is a problem + workspace.flushBuildOrder(); + info = (ProjectInfo) getResourceInfo(false, true); + info.set(M_OPEN); + //clear the unknown children immediately to avoid background refresh + boolean unknownChildren = info.isSet(M_CHILDREN_UNKNOWN); + if (unknownChildren) + info.clear(M_CHILDREN_UNKNOWN); + // the M_USED flag is used to indicate the difference between opening a project + // for the first time and opening it from a previous close (restoring it from disk) + boolean used = info.isSet(M_USED); + boolean snapshotLoaded = false; + if (!used && !workspace.getMetaArea().getRefreshLocationFor(this).toFile().exists()) { + //auto-load a refresh snapshot if it is set + final boolean hasSavedDescription = getLocalManager().hasSavedDescription(this); + if (hasSavedDescription) { + ProjectDescription updatedDesc = info.getDescription(); + if (updatedDesc != null) { + URI autoloadURI = updatedDesc.getSnapshotLocationURI(); + if (autoloadURI != null) { + try { + autoloadURI = getPathVariableManager().resolveURI(autoloadURI); + internalLoadSnapshot(SNAPSHOT_TREE, autoloadURI, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); + snapshotLoaded = true; + } catch (CoreException ce) { + //Log non-existing autoload snapshot as warning only + String msgerr = NLS.bind(Messages.projRead_cannotReadSnapshot, getName(), ce.getLocalizedMessage()); + Policy.log(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, msgerr)); + } + } + } + } + } + boolean minorIssuesDuringRestore = false; + if (used) { + minorIssuesDuringRestore = workspace.getSaveManager().restore(this, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + } else { + info.set(M_USED); + //reconcile any links and groups in the project description + IStatus result = reconcileLinksAndGroups(info.getDescription()); + if (!result.isOK()) + throw new CoreException(result); + workspace.updateModificationStamp(info); + monitor.worked(Policy.opWork * (snapshotLoaded ? 15 : 20) / 100); + } + startup(); + //request a refresh if the project is new and has unknown members on disk + // or restore of the project is not fully successful + if ((!used && unknownChildren) || !minorIssuesDuringRestore) { + boolean refreshed = false; + if (!used) { + refreshed = workspace.getSaveManager().restoreFromRefreshSnapshot(this, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + if (refreshed) { // account for the refresh work + monitor.worked(Policy.opWork * 60 / 100); + } + } + //refresh either in background or foreground + if (!refreshed) { + if ((updateFlags & IResource.BACKGROUND_REFRESH) != 0) { + workspace.refreshManager.refresh(this); + monitor.worked(Policy.opWork * 60 / 100); + } else { + refreshLocal(IResource.DEPTH_INFINITE, Policy.subMonitorFor(monitor, Policy.opWork * 60 / 100)); + } + } + } + //creation of this project may affect overlapping resources + workspace.getAliasManager().updateAliases(this, getStore(), IResource.DEPTH_INFINITE, monitor); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true); + } + } finally { + monitor.done(); + } + } + + @Override + public void open(IProgressMonitor monitor) throws CoreException { + open(IResource.NONE, monitor); + } + + /** + * The project description file has changed on disk, resulting in a changed + * set of linked resources. Perform the necessary creations and deletions of + * links to bring the links in sync with those described in the project description. + * @param newDescription the new project description that may have + * changed link descriptions. + * @return status ok if everything went well, otherwise an ERROR multi-status + * describing the problems encountered. + */ + public IStatus reconcileLinksAndGroups(ProjectDescription newDescription) { + String msg = Messages.links_errorLinkReconcile; + HashMap newLinks = newDescription.getLinks(); + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.OPERATION_FAILED, msg, null); + //walk over old linked resources and remove those that are no longer defined + ProjectDescription oldDescription = internalGetDescription(); + if (oldDescription != null) { + HashMap oldLinks = oldDescription.getLinks(); + if (oldLinks != null) { + for (Iterator it = oldLinks.values().iterator(); it.hasNext();) { + LinkDescription oldLink = it.next(); + Resource oldLinkResource = (Resource) findMember(oldLink.getProjectRelativePath()); + if (oldLinkResource == null || !oldLinkResource.isLinked()) + continue; + LinkDescription newLink = null; + if (newLinks != null) + newLink = newLinks.get(oldLink.getProjectRelativePath()); + //if the new link is missing, or has different (raw) location or gender, then remove old link + if (newLink == null || !newLink.getLocationURI().equals(oldLinkResource.getRawLocationURI()) || newLink.getType() != oldLinkResource.getType()) { + try { + oldLinkResource.delete(IResource.NONE, null); + //refresh the resource, because removing a link can reveal a previously hidden resource in parent + oldLinkResource.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } + } + } + } + // walk over new links and groups and create if necessary + // Recreate them in order of the higher up in the tree hierarchy first, + // so we don't have to create intermediate directories that would turn + // out + // to be groups or link folders. + if (newLinks == null) + return status; + //sort links to avoid creating nested links before their parents + TreeSet newLinksAndGroups = new TreeSet<>(new Comparator() { + @Override + public int compare(LinkDescription arg0, LinkDescription arg1) { + int numberOfSegments0 = arg0.getProjectRelativePath().segmentCount(); + int numberOfSegments1 = arg1.getProjectRelativePath().segmentCount(); + if (numberOfSegments0 != numberOfSegments1) + return numberOfSegments0 - numberOfSegments1; + else if (arg0.equals(arg1)) + return 0; + + return -1; + } + + }); + if (newLinks != null) + newLinksAndGroups.addAll(newLinks.values()); + + for (Iterator it = newLinksAndGroups.iterator(); it.hasNext();) { + Object description = it.next(); + LinkDescription newLink = (LinkDescription) description; + try { + Resource toLink = workspace.newResource(getFullPath().append(newLink.getProjectRelativePath()), newLink.getType()); + IContainer parent = toLink.getParent(); + if (parent != null && !parent.exists() && parent.getType() == FOLDER) + ((Folder) parent).ensureExists(Policy.monitorFor(null)); + if (!toLink.exists() || !toLink.isLinked()) { + if (newLink.isGroup()) + ((Folder) toLink).create(IResource.REPLACE | IResource.VIRTUAL, true, null); + else + toLink.createLink(newLink.getLocationURI(), IResource.REPLACE | IResource.ALLOW_MISSING_LOCAL, null); + } + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } + return status; + } + + @Override + public void saveSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + //Project must be open such that variables can be resolved + checkAccessible(getFlags(getResourceInfo(false, false))); + //URI must not be null and must not refer to undefined path variables + URI resolvedSnapshotLocation = getPathVariableManager().resolveURI(snapshotLocation); + if (resolvedSnapshotLocation == null || !resolvedSnapshotLocation.isAbsolute()) { + String message = NLS.bind(Messages.projRead_badSnapshotLocation, resolvedSnapshotLocation); + throw new CoreException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, message, null)); + } + if ((options & SNAPSHOT_TREE) != 0) { + // write a snapshot of refresh information + try { + IProgressMonitor sub = Policy.subMonitorFor(monitor, Policy.opWork / 2); + workspace.getSaveManager().saveRefreshSnapshot(this, resolvedSnapshotLocation, sub); + } catch (OperationCanceledException e) { + //workspace.getWorkManager().operationCanceled(); + throw e; + } + } + if ((options & SNAPSHOT_SET_AUTOLOAD) != 0) { + IProgressMonitor sub = Policy.subMonitorFor(monitor, Policy.opWork / 2); + //Make absolute URI inside the project relative + if (snapshotLocation != null && snapshotLocation.isAbsolute()) { + snapshotLocation = getPathVariableManager().convertToRelative(snapshotLocation, false, "PROJECT_LOC"); //$NON-NLS-1$ + } + IProjectDescription desc = getDescription(); + ((ProjectDescription) desc).setSnapshotLocationURI(snapshotLocation); + setDescription(desc, IResource.KEEP_HISTORY | IResource.AVOID_NATURE_CONFIG, sub); + } + } finally { + monitor.done(); + } + } + + @Override + public void setDescription(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + // FIXME - update flags should be honored: + // KEEP_HISTORY means capture .project file in local history + // FORCE means overwrite any existing .project file + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask(Messages.resources_setDesc, Policy.totalWork); + ISchedulingRule rule = null; + if ((updateFlags & IResource.AVOID_NATURE_CONFIG) != 0) + rule = workspace.getRuleFactory().modifyRule(this); + else + rule = workspace.getRoot(); + try { + //need to use root rule because nature configuration calls third party code + workspace.prepareOperation(rule, monitor); + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + //if nothing has changed, we don't need to do anything + ProjectDescription oldDescription = internalGetDescription(); + ProjectDescription newDescription = (ProjectDescription) description; + boolean hasPublicChanges = oldDescription.hasPublicChanges(newDescription); + boolean hasPrivateChanges = oldDescription.hasPrivateChanges(newDescription); + if (!hasPublicChanges && !hasPrivateChanges) + return; + checkDescription(this, newDescription, false); + //If we're out of sync and !FORCE, then fail. + //If the file is missing, we want to write the new description then throw an exception. + boolean hadSavedDescription = true; + if (((updateFlags & IResource.FORCE) == 0)) { + hadSavedDescription = getLocalManager().hasSavedDescription(this); + if (hadSavedDescription && !getLocalManager().isDescriptionSynchronized(this)) { + String message = NLS.bind(Messages.resources_projectDescSync, getName()); + throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, getFullPath(), message, null); + } + } + //see if we have an old .prj file + if (!hadSavedDescription) + hadSavedDescription = workspace.getMetaArea().hasSavedProject(this); + workspace.beginOperation(true); + MultiStatus status = basicSetDescription(newDescription, updateFlags); + if (hadSavedDescription && !status.isOK()) + throw new CoreException(status); + //write the new description to the .project file + writeDescription(oldDescription, updateFlags, hasPublicChanges, hasPrivateChanges); + //increment the content id even for private changes + info = getResourceInfo(false, true); + info.incrementContentId(); + workspace.updateModificationStamp(info); + if (!hadSavedDescription) { + String msg = NLS.bind(Messages.resources_missingProjectMetaRepaired, getName()); + status.merge(new ResourceStatus(IResourceStatus.MISSING_DESCRIPTION_REPAIRED, getFullPath(), msg)); + } + if (!status.isOK()) + throw new CoreException(status); + } finally { + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); + workspace.endOperation(rule, true); + } + } finally { + monitor.done(); + } + } + + @Override + public void setDescription(IProjectDescription description, IProgressMonitor monitor) throws CoreException { + // funnel all operations to central method + setDescription(description, IResource.KEEP_HISTORY, monitor); + } + + /** + * Restore the non-persisted state for the project. For example, read and set + * the description from the local meta area. Also, open the property store etc. + * This method is used when an open project is restored and so emulates + * the behaviour of open(). + */ + protected void startup() throws CoreException { + if (!isOpen()) + return; + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_OPEN, this)); + } + + @Override + public void touch(IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + String message = NLS.bind(Messages.resources_touch, getFullPath()); + monitor.beginTask(message, Policy.totalWork); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, monitor); + workspace.beginOperation(true); + super.touch(Policy.subMonitorFor(monitor, Policy.opWork)); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); + workspace.endOperation(rule, true); + } + } finally { + monitor.done(); + } + } + + /** + * The project description file on disk is better than the description in memory. + * Make sure the project description in memory is synchronized with the + * description file contents. + */ + protected void updateDescription() throws CoreException { + if (ProjectDescription.isWriting) + return; + ProjectDescription.isReading = true; + try { + ProjectDescription description = getLocalManager().read(this, false); + //links can only be created if the project is open + IStatus result = null; + if (isOpen()) + result = reconcileLinksAndGroups(description); + internalSetDescription(description, true); + if (result != null && !result.isOK()) + throw new CoreException(result); + } finally { + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); + ProjectDescription.isReading = false; + } + } + + /** + * Writes the project's current description file to disk. + */ + public void writeDescription(int updateFlags) throws CoreException { + writeDescription(internalGetDescription(), updateFlags, true, true); + } + + /** + * Writes the project description file to disk. This is the only method + * that should ever be writing the description, because it ensures that + * the description isn't then immediately discovered as an incoming + * change and read back from disk. + * @param description The description to write + * @param updateFlags The write operation update flags + * @param hasPublicChanges Whether the public sections of the description have changed + * @param hasPrivateChanges Whether the private sections of the description have changed + * @exception CoreException On failure to write the description + */ + public void writeDescription(IProjectDescription description, int updateFlags, boolean hasPublicChanges, boolean hasPrivateChanges) throws CoreException { + if (ProjectDescription.isReading) + return; + ProjectDescription.isWriting = true; + try { + getLocalManager().internalWrite(this, description, updateFlags, hasPublicChanges, hasPrivateChanges); + } finally { + ProjectDescription.isWriting = false; + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java new file mode 100644 index 0000000000..8965515cb4 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectContentTypes.java @@ -0,0 +1,245 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.internal.utils.Cache; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ProjectScope; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy; +import org.eclipse.core.runtime.content.IContentTypeMatcher; +import org.eclipse.core.runtime.preferences.*; +import org.osgi.service.prefs.BackingStoreException; +import org.osgi.service.prefs.Preferences; + +/** + * Manages project-specific content type behavior. + * + * @see ContentDescriptionManager + * @see org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy + * @since 3.1 + */ +public class ProjectContentTypes { + + /** + * A project-aware content type selection policy. + * This class is also a dynamic scope context that will delegate to either + * project or instance scope depending on whether project specific settings were enabled + * for the project in question. + */ + private class ProjectContentTypeSelectionPolicy implements ISelectionPolicy, IScopeContext { + // corresponding project + private Project project; + // cached project scope + private IScopeContext projectScope; + + public ProjectContentTypeSelectionPolicy(Project project) { + this.project = project; + this.projectScope = new ProjectScope(project); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof IScopeContext)) + return false; + IScopeContext other = (IScopeContext) obj; + if (!getName().equals(other.getName())) + return false; + IPath location = getLocation(); + return location == null ? other.getLocation() == null : location.equals(other.getLocation()); + } + + private IScopeContext getDelegate() { + if (!usesContentTypePreferences(project.getName())) + return InstanceScope.INSTANCE; + return projectScope; + } + + @Override + public IPath getLocation() { + return getDelegate().getLocation(); + } + + @Override + public String getName() { + return getDelegate().getName(); + } + + @Override + public IEclipsePreferences getNode(String qualifier) { + return getDelegate().getNode(qualifier); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public IContentType[] select(IContentType[] candidates, boolean fileName, boolean content) { + return ProjectContentTypes.this.select(project, candidates, fileName, content); + } + } + + private static final String CONTENT_TYPE_PREF_NODE = "content-types"; //$NON-NLS-1$ + + private static final String PREF_LOCAL_CONTENT_TYPE_SETTINGS = "enabled"; //$NON-NLS-1$ + private static final Preferences PROJECT_SCOPE = Platform.getPreferencesService().getRootNode().node(ProjectScope.SCOPE); + private Cache contentTypesPerProject; + private Workspace workspace; + + static boolean usesContentTypePreferences(String projectName) { + try { + // be careful looking up for our node so not to create any nodes as side effect + Preferences node = PROJECT_SCOPE; + //TODO once bug 90500 is fixed, should be simpler + // for now, take the long way + if (!node.nodeExists(projectName)) + return false; + node = node.node(projectName); + if (!node.nodeExists(Platform.PI_RUNTIME)) + return false; + node = node.node(Platform.PI_RUNTIME); + if (!node.nodeExists(CONTENT_TYPE_PREF_NODE)) + return false; + node = node.node(CONTENT_TYPE_PREF_NODE); + return node.getBoolean(PREF_LOCAL_CONTENT_TYPE_SETTINGS, false); + } catch (BackingStoreException e) { + // exception treated when retrieving the project preferences + } + return false; + } + + public ProjectContentTypes(Workspace workspace) { + this.workspace = workspace; + // keep cache small + this.contentTypesPerProject = new Cache(5, 30, 0.4); + } + + /** + * Collect content types associated to the natures configured for the given project. + */ + private Set collectAssociatedContentTypes(Project project) { + String[] enabledNatures = workspace.getNatureManager().getEnabledNatures(project); + if (enabledNatures.length == 0) + return Collections.EMPTY_SET; + Set related = new HashSet<>(enabledNatures.length); + for (int i = 0; i < enabledNatures.length; i++) { + ProjectNatureDescriptor descriptor = (ProjectNatureDescriptor) workspace.getNatureDescriptor(enabledNatures[i]); + if (descriptor == null) + // no descriptor found for the nature, skip it + continue; + String[] natureContentTypes = descriptor.getContentTypeIds(); + for (int j = 0; j < natureContentTypes.length; j++) + // collect associate content types + related.add(natureContentTypes[j]); + } + return related; + } + + public void contentTypePreferencesChanged(IProject project) { + final ProjectInfo info = (ProjectInfo) ((Project) project).getResourceInfo(false, false); + if (info != null) + info.setMatcher(null); + } + + /** + * Creates a content type matcher for the given project. Takes natures and user settings into account. + */ + private IContentTypeMatcher createMatcher(Project project) { + ProjectContentTypeSelectionPolicy projectContentTypeSelectionPolicy = new ProjectContentTypeSelectionPolicy(project); + return Platform.getContentTypeManager().getMatcher(projectContentTypeSelectionPolicy, projectContentTypeSelectionPolicy); + } + + @SuppressWarnings({"unchecked"}) + private Set getAssociatedContentTypes(Project project) { + final ResourceInfo info = project.getResourceInfo(false, false); + if (info == null) + // the project has been deleted + return null; + final String projectName = project.getName(); + synchronized (contentTypesPerProject) { + Cache.Entry entry = contentTypesPerProject.getEntry(projectName); + if (entry != null) + // we have an entry... + if (entry.getTimestamp() == info.getContentId()) + // ...and it is not stale, so just return it + return (Set) entry.getCached(); + // no cached information found, have to collect associated content types + Set result = collectAssociatedContentTypes(project); + if (entry == null) + // there was no entry before - create one + entry = contentTypesPerProject.addEntry(projectName, result, info.getContentId()); + else { + // just update the existing entry + entry.setTimestamp(info.getContentId()); + entry.setCached(result); + } + return result; + } + } + + public IContentTypeMatcher getMatcherFor(Project project) throws CoreException { + ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, false); + //fail if project has been deleted concurrently + if (info == null) + project.checkAccessible(project.getFlags(info)); + IContentTypeMatcher matcher = info.getMatcher(); + if (matcher != null) + return matcher; + matcher = createMatcher(project); + info.setMatcher(matcher); + return matcher; + } + + /** + * Implements project specific, nature-based selection policy. No content types are vetoed. + * + * The criteria for this policy is as follows: + *
                    + *
                  1. associated content types should appear before non-associated content types
                  2. + *
                  3. otherwise, relative ordering should be preserved.
                  4. + *
                  + * + * @see org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy + */ + final IContentType[] select(Project project, IContentType[] candidates, boolean fileName, boolean content) { + // since no vetoing is done here, don't go further if there is nothing to sort + if (candidates.length < 2) + return candidates; + final Set associated = getAssociatedContentTypes(project); + if (associated == null || associated.isEmpty()) + // project has no content types associated + return candidates; + int associatedCount = 0; + for (int i = 0; i < candidates.length; i++) + // is it an associated content type? + if (associated.contains(candidates[i].getId())) { + // need to move it to the right spot (unless all types visited so far are associated as well) + if (associatedCount < i) { + final IContentType promoted = candidates[i]; + // move all non-associated content types before it one one position up... + for (int j = i; j > associatedCount; j--) + candidates[j] = candidates[j - 1]; + // ...so there is an empty spot for the content type we are promoting + candidates[associatedCount] = promoted; + } + associatedCount++; + } + return candidates; + } +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java new file mode 100644 index 0000000000..1af05fa79a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescription.java @@ -0,0 +1,960 @@ +/******************************************************************************* + * 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) - [245937] setLinkLocation() detects non-change + * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * Broadcom Corporation - build configurations and references + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.util.*; +import java.util.Map.Entry; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +public class ProjectDescription extends ModelObject implements IProjectDescription { + // constants + private static final IBuildConfiguration[] EMPTY_BUILD_CONFIG_REFERENCE_ARRAY = new IBuildConfiguration[0]; + private static final ICommand[] EMPTY_COMMAND_ARRAY = new ICommand[0]; + private static final IProject[] EMPTY_PROJECT_ARRAY = new IProject[0]; + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final String EMPTY_STR = ""; //$NON-NLS-1$ + + protected static boolean isReading = false; + + //flags to indicate when we are in the middle of reading or writing a + // workspace description + //these can be static because only one description can be read at once. + protected static boolean isWriting = false; + protected ICommand[] buildSpec = EMPTY_COMMAND_ARRAY; + protected String comment = EMPTY_STR; + + // Build configuration + References state + /** Id of the currently active build configuration */ + protected String activeConfiguration = IBuildConfiguration.DEFAULT_CONFIG_NAME; + /** + * The 'real' build configuration names set on this project. + * This doesn't contain the generated 'default' build configuration with name + * {@link IBuildConfiguration#DEFAULT_CONFIG_NAME} + * when no build configurations have been defined. + */ + protected String[] configNames = EMPTY_STRING_ARRAY; + // Static + Dynamic project level references + protected IProject[] staticRefs = EMPTY_PROJECT_ARRAY; + protected IProject[] dynamicRefs = EMPTY_PROJECT_ARRAY; + /** Map from config name in this project -> build configurations in other projects */ + protected HashMap dynamicConfigRefs = new HashMap<>(1); + + // Cache of the build configurations + protected volatile IBuildConfiguration[] cachedBuildConfigs; + // Cached build configuration references. Not persisted. + protected Map cachedConfigRefs = Collections.synchronizedMap(new HashMap(1)); + /** + * Cached project level references. Synchronize on {@link #cachedRefsMutex} before reading or writing. Increment + * {@link #cachedRefsDirtyCount} whenever this is dirtied. + */ + protected IProject[] cachedRefs; + /** + * Counts the number of times {@link #cachedRefs} has been dirtied. Can be used to determine if dynamic dependencies have + * changed during an operation that is intended to be atomic with respect to dynamic dependencies. Synchronize on + * {@link #cachedRefsMutex} before accessing. + */ + protected int cachedRefsDirtyCount; + /** + * Mutex used to protect {@link #cachedRefs} and {@link #cachedRefsDirtyCount}. + */ + protected final Object cachedRefsMutex = new Object(); + + /** + * Map of (IPath -> LinkDescription) pairs for each linked resource + * in this project, where IPath is the project relative path of the resource. + */ + protected HashMap linkDescriptions = null; + + /** + * Map of (IPath -> LinkedList) pairs for each filtered resource + * in this project, where IPath is the project relative path of the resource. + */ + protected HashMap> filterDescriptions = null; + + /** + * Map of (String -> VariableDescription) pairs for each variable in this + * project, where String is the name of the variable. + */ + protected HashMap variableDescriptions = null; + + // fields + protected URI location = null; + protected String[] natures = EMPTY_STRING_ARRAY; + protected URI snapshotLocation = null; + + public ProjectDescription() { + super(); + } + + @Override + @SuppressWarnings({"unchecked"}) + public Object clone() { + ProjectDescription clone = (ProjectDescription) super.clone(); + //don't want the clone to have access to our internal link locations table or builders + clone.linkDescriptions = null; + clone.filterDescriptions = null; + if (variableDescriptions != null) + clone.variableDescriptions = (HashMap) variableDescriptions.clone(); + clone.buildSpec = getBuildSpec(true); + clone.dynamicConfigRefs = (HashMap) dynamicConfigRefs.clone(); + clone.cachedConfigRefs = Collections.synchronizedMap(new HashMap(1)); + clone.clearCachedDynamicReferences(null); + return clone; + } + + /** + * Clear cached references for the specified build config name + * or all if configName is null. + */ + public void clearCachedDynamicReferences(String configName) { + synchronized (cachedRefsMutex) { + if (configName == null) + cachedConfigRefs.clear(); + else + cachedConfigRefs.remove(configName); + cachedRefs = null; + cachedRefsDirtyCount++; + } + } + + /** + * Returns a copy of the given array of build configs with all duplicates removed + */ + private IBuildConfiguration[] copyAndRemoveDuplicates(IBuildConfiguration[] values) { + Set set = new LinkedHashSet<>(Arrays.asList(values)); + return set.toArray(new IBuildConfiguration[set.size()]); + } + + /** + * Returns a copy of the given array with all duplicates removed + */ + private IProject[] copyAndRemoveDuplicates(IProject[] projects) { + IProject[] result = new IProject[projects.length]; + int count = 0; + next: for (int i = 0; i < projects.length; i++) { + IProject project = projects[i]; + // scan to see if there are any other projects by the same name + for (int j = 0; j < count; j++) + if (project.equals(result[j])) + continue next; + // not found + result[count++] = project; + } + if (count < projects.length) { + //shrink array + IProject[] reduced = new IProject[count]; + System.arraycopy(result, 0, reduced, 0, count); + return reduced; + } + return result; + } + + /** + * Helper to turn an array of projects into an array of {@link IBuildConfiguration} to the + * projects' active configuration + * Order is preserved - the buildConfigs appear for each project in the order + * that the projects were specified. + * @param projects projects to get the active configuration from + * @return collection of build config references + */ + private Collection getBuildConfigReferencesFromProjects(IProject[] projects) { + List refs = new ArrayList<>(projects.length); + for (int i = 0; i < projects.length; i++) + refs.add(new BuildConfiguration(projects[i], null)); + return refs; + } + + /** + * Helper to fetch projects from an array of build configuration references + * @param refs + * @return List + */ + private Collection getProjectsFromBuildConfigRefs(IBuildConfiguration[] refs) { + LinkedHashSet projects = new LinkedHashSet<>(refs.length); + for (int i = 0; i < refs.length; i++) + projects.add(refs[i].getProject()); + return projects; + } + + public String getActiveBuildConfig() { + return activeConfiguration; + } + + /** + * Returns the union of the description's static and dynamic project references, + * with duplicates omitted. The calculation is optimized by caching the result + * Call the configuration based implementation. + * @see #getAllBuildConfigReferences(String, boolean) + */ + public IProject[] getAllReferences(boolean makeCopy) { + int dirtyCount; + IProject[] projRefs; + + synchronized (cachedRefsMutex) { + projRefs = cachedRefs; + dirtyCount = cachedRefsDirtyCount; + } + // Retry this computation until we're able to proceed to the end without someone dirtying the cache. + // This loop is here to prevent us from caching a stale result if someone dirties the cache between + // the time we invoke getAllBuildConfigReferences and the time we can write to cachedRefs. + while (projRefs == null) { + IBuildConfiguration[] refs; + if (hasBuildConfig(activeConfiguration)) + refs = getAllBuildConfigReferences(activeConfiguration, false); + else if (configNames.length > 0) + refs = getAllBuildConfigReferences(configNames[0], false); + else + // No build configuration => fall-back to default + refs = getAllBuildConfigReferences(IBuildConfiguration.DEFAULT_CONFIG_NAME, false); + Collection l = getProjectsFromBuildConfigRefs(refs); + + synchronized (cachedRefsMutex) { + // If nobody dirtied the cache since the start of this operation then we can cache the + // new result and end the loop. + if (cachedRefsDirtyCount == dirtyCount) { + cachedRefs = l.toArray(new IProject[l.size()]); + } + projRefs = cachedRefs; + dirtyCount = cachedRefsDirtyCount; + } + } + //still need to copy the result to prevent tampering with the cache + return makeCopy ? (IProject[]) projRefs.clone() : projRefs; + } + + /** + * The main entrance point to fetch the full set of Project references. + * + * Returns the union of all the description's references. Includes static and dynamic + * project level references as well as build configuration references for the configuration + * with the given id. + * Duplicates are omitted. The calculation is optimized by caching the result. + * Note that these BuildConfiguration references may have null name. They must + * be resolved using {@link BuildConfiguration#getBuildConfig()} before use. + * Returns an empty array if the given configName does not exist in the description. + */ + public IBuildConfiguration[] getAllBuildConfigReferences(String configName, boolean makeCopy) { + if (!hasBuildConfig(configName)) + return EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; + IBuildConfiguration[] refs = cachedConfigRefs.get(configName); + if (refs == null) { + Set references = new LinkedHashSet<>(); + IBuildConfiguration[] dynamicBuildConfigs = dynamicConfigRefs.containsKey(configName) ? dynamicConfigRefs.get(configName) : EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(getName()); + Collection dynamic; + try { + IBuildConfiguration buildConfig = project.getBuildConfig(configName); + dynamic = getBuildConfigReferencesFromProjects(computeDynamicReferencesForProject(buildConfig, getBuildSpec())); + } catch (CoreException e) { + dynamic = Collections.emptyList(); + } + Collection legacyDynamic = getBuildConfigReferencesFromProjects(dynamicRefs); + Collection statik = getBuildConfigReferencesFromProjects(staticRefs); + + // Combine all references: + // New build config references (which only come in dynamic form) trump all others. + references.addAll(Arrays.asList(dynamicBuildConfigs)); + // We preserve the previous order of static project references before dynamic project references + references.addAll(statik); + references.addAll(legacyDynamic); + references.addAll(dynamic); + refs = references.toArray(new IBuildConfiguration[references.size()]); + cachedConfigRefs.put(configName, refs); + } + return makeCopy ? (IBuildConfiguration[]) refs.clone() : refs; + } + + /** + * Used by Project to get the buildConfigs on the description. + * @return the project configurations + */ + public IBuildConfiguration[] getBuildConfigs(IProject project, boolean makeCopy) { + IBuildConfiguration[] configs = cachedBuildConfigs; + // Ensure project is up to date in the cache + if (configs != null && !project.equals(configs[0].getProject())) + configs = null; + if (configs == null) { + if (configNames.length == 0) + configs = new IBuildConfiguration[] {new BuildConfiguration(project)}; + else { + configs = new IBuildConfiguration[configNames.length]; + for (int i = 0; i < configs.length; i++) + configs[i] = new BuildConfiguration(project, configNames[i]); + } + cachedBuildConfigs = configs; + } + return makeCopy ? (IBuildConfiguration[]) configs.clone() : configs; + } + + @Override + public IBuildConfiguration[] getBuildConfigReferences(String configName) { + return getBuildConfigRefs(configName, true); + } + + public IBuildConfiguration[] getBuildConfigRefs(String configName, boolean makeCopy) { + if (!hasBuildConfig(configName) || !dynamicConfigRefs.containsKey(configName)) + return EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; + + return makeCopy ? (IBuildConfiguration[]) dynamicConfigRefs.get(configName).clone() : dynamicConfigRefs.get(configName); + } + + /** + * Returns the build configuration references map + * @param makeCopy + */ + @SuppressWarnings({"unchecked"}) + public Map getBuildConfigReferences(boolean makeCopy) { + return makeCopy ? (Map) dynamicConfigRefs.clone() : dynamicConfigRefs; + } + + @Override + public ICommand[] getBuildSpec() { + return getBuildSpec(true); + } + + public ICommand[] getBuildSpec(boolean makeCopy) { + //thread safety: copy reference in case of concurrent write + ICommand[] oldCommands = this.buildSpec; + if (oldCommands == null) + return EMPTY_COMMAND_ARRAY; + if (!makeCopy) + return oldCommands; + ICommand[] result = new ICommand[oldCommands.length]; + for (int i = 0; i < result.length; i++) + result[i] = (ICommand) ((BuildCommand) oldCommands[i]).clone(); + return result; + } + + @Override + public String getComment() { + return comment; + } + + @Override + public IProject[] getDynamicReferences() { + return getDynamicReferences(true); + } + + public IProject[] getDynamicReferences(boolean makeCopy) { + return makeCopy ? (IProject[]) dynamicRefs.clone() : dynamicRefs; + } + + /** + * Returns the link location for the given resource name. Returns null if + * no such link exists. + */ + public URI getLinkLocationURI(IPath aPath) { + if (linkDescriptions == null) + return null; + LinkDescription desc = linkDescriptions.get(aPath); + return desc == null ? null : desc.getLocationURI(); + } + + /** + * Returns the filter for the given resource name. Returns null if + * no such filter exists. + */ + synchronized public LinkedList getFilter(IPath aPath) { + if (filterDescriptions == null) + return null; + return filterDescriptions.get(aPath); + } + + /** + * Returns the map of link descriptions (IPath (project relative path) -> LinkDescription). + * Since this method is only used internally, it never creates a copy. + * Returns null if the project does not have any linked resources. + */ + public HashMap getLinks() { + return linkDescriptions; + } + + /** + * Returns the map of filter descriptions (IPath (project relative path) -> LinkedList). + * Since this method is only used internally, it never creates a copy. + * Returns null if the project does not have any filtered resources. + */ + public HashMap> getFilters() { + return filterDescriptions; + } + + /** + * Returns the map of variable descriptions (String (variable name) -> + * VariableDescription). Since this method is only used internally, it never + * creates a copy. Returns null if the project does not have any variables. + */ + public HashMap getVariables() { + return variableDescriptions; + } + + /** + * @see IProjectDescription#getLocation() + * @deprecated + */ + @Override + @Deprecated + public IPath getLocation() { + if (location == null) + return null; + return FileUtil.toPath(location); + } + + @Override + public URI getLocationURI() { + return location; + } + + @Override + public String[] getNatureIds() { + return getNatureIds(true); + } + + public String[] getNatureIds(boolean makeCopy) { + if (natures == null) + return EMPTY_STRING_ARRAY; + return makeCopy ? (String[]) natures.clone() : natures; + } + + @Override + public IProject[] getReferencedProjects() { + return getReferencedProjects(true); + } + + public IProject[] getReferencedProjects(boolean makeCopy) { + if (staticRefs == null) + return EMPTY_PROJECT_ARRAY; + return makeCopy ? (IProject[]) staticRefs.clone() : staticRefs; + } + + /** + * Returns the URI to load a resource snapshot from. + * May return null if no snapshot is set. + *

                  + * EXPERIMENTAL. This constant has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. Please do not use this API without + * consulting with the Platform Core team. + *

                  + * @return the snapshot location URI, + * or null. + * @see IProject#loadSnapshot(int, URI, IProgressMonitor) + * @see #setSnapshotLocationURI(URI) + * @since 3.6 + */ + public URI getSnapshotLocationURI() { + return snapshotLocation; + } + + @Override + public boolean hasNature(String natureID) { + String[] natureIDs = getNatureIds(false); + for (int i = 0; i < natureIDs.length; ++i) + if (natureIDs[i].equals(natureID)) + return true; + return false; + } + + /** + * Helper method to compare two maps of Configuration Name -> IBuildConfigurationReference[] + * @return boolean indicating if there are differences between the two maps + */ + private static boolean configRefsHaveChanges(Map m1, Map m2) { + if (m1.size() != m2.size()) + return true; + for (Iterator> it = m1.entrySet().iterator(); it.hasNext();) { + Entry e = it.next(); + if (!m2.containsKey(e.getKey())) + return true; + if (!Arrays.equals(e.getValue(), m2.get(e.getKey()))) + return true; + } + return false; + } + + /** + * Internal method to check if the description has a given build configuration. + */ + boolean hasBuildConfig(String buildConfigName) { + Assert.isNotNull(buildConfigName); + if (configNames.length == 0) + return IBuildConfiguration.DEFAULT_CONFIG_NAME.equals(buildConfigName); + for (int i = 0; i < configNames.length; i++) + if (configNames[i].equals(buildConfigName)) + return true; + return false; + } + + /** + * Returns true if any private attributes of the description have changed. + * Private attributes are those that are not stored in the project description + * file (.project). + */ + public boolean hasPrivateChanges(ProjectDescription description) { + if (location == null) { + if (description.location != null) + return true; + } else if (!location.equals(description.location)) + return true; + + if (!Arrays.equals(dynamicRefs, description.dynamicRefs)) + return true; + + // Build Configuration state + if (!activeConfiguration.equals(description.activeConfiguration)) + return true; + if (!Arrays.equals(configNames, description.configNames)) + return true; + // Configuration level references + if (configRefsHaveChanges(dynamicConfigRefs, description.dynamicConfigRefs)) + return true; + + return false; + } + + /** + * Returns true if any public attributes of the description have changed. + * Public attributes are those that are stored in the project description + * file (.project). + */ + public boolean hasPublicChanges(ProjectDescription description) { + if (!getName().equals(description.getName())) + return true; + if (!comment.equals(description.getComment())) + return true; + //don't bother optimizing if the order has changed + if (!Arrays.equals(buildSpec, description.getBuildSpec(false))) + return true; + if (!Arrays.equals(staticRefs, description.getReferencedProjects(false))) + return true; + if (!Arrays.equals(natures, description.getNatureIds(false))) + return true; + + HashMap> otherFilters = description.getFilters(); + if ((filterDescriptions == null) && (otherFilters != null)) + return otherFilters != null; + if ((filterDescriptions != null) && !filterDescriptions.equals(otherFilters)) + return true; + + HashMap otherVariables = description.getVariables(); + if ((variableDescriptions == null) && (otherVariables != null)) + return true; + if ((variableDescriptions != null) && !variableDescriptions.equals(otherVariables)) + return true; + + final HashMap otherLinks = description.getLinks(); + if (linkDescriptions != otherLinks) { + if (linkDescriptions == null || !linkDescriptions.equals(otherLinks)) + return true; + } + + final URI otherSnapshotLoc = description.getSnapshotLocationURI(); + if (snapshotLocation != otherSnapshotLoc) { + if (snapshotLocation == null || !snapshotLocation.equals(otherSnapshotLoc)) + return true; + } + return false; + } + + @Override + public ICommand newCommand() { + return new BuildCommand(); + } + + @Override + public void setActiveBuildConfig(String configName) { + Assert.isNotNull(configName); + if (!configName.equals(activeConfiguration)) + clearCachedDynamicReferences(null); + activeConfiguration = configName; + } + + @Override + public void setBuildSpec(ICommand[] value) { + Assert.isLegal(value != null); + //perform a deep copy in case clients perform further changes to the command + ICommand[] result = new ICommand[value.length]; + for (int i = 0; i < result.length; i++) { + result[i] = (ICommand) ((BuildCommand) value[i]).clone(); + //copy the reference to any builder instance from the old build spec + //to preserve builder states if possible. + for (int j = 0; j < buildSpec.length; j++) { + if (result[i].equals(buildSpec[j])) { + ((BuildCommand) result[i]).setBuilders(((BuildCommand) buildSpec[j]).getBuilders()); + break; + } + } + } + buildSpec = result; + } + + @Override + public void setComment(String value) { + comment = value; + } + + @Deprecated + @Override + public void setDynamicReferences(IProject[] value) { + Assert.isLegal(value != null); + dynamicRefs = copyAndRemoveDuplicates(value); + clearCachedDynamicReferences(null); + } + + public void setBuildConfigReferences(HashMap refs) { + dynamicConfigRefs = new HashMap<>(refs); + clearCachedDynamicReferences(null); + } + + @Override + public void setBuildConfigReferences(String configName, IBuildConfiguration[] references) { + Assert.isLegal(configName != null); + Assert.isLegal(references != null); + if (!hasBuildConfig(configName)) + return; + dynamicConfigRefs.put(configName, copyAndRemoveDuplicates(references)); + clearCachedDynamicReferences(configName); + } + + @Override + public void setBuildConfigs(String[] names) { + // Remove references for deleted buildConfigs + LinkedHashSet buildConfigNames = new LinkedHashSet<>(); + + if (names == null || names.length == 0) { + configNames = EMPTY_STRING_ARRAY; + buildConfigNames.add(IBuildConfiguration.DEFAULT_CONFIG_NAME); + } else { + // Filter out duplicates + for (String n : names) { + Assert.isLegal(n != null); + buildConfigNames.add(n); + } + + if (buildConfigNames.size() == 1 && ((buildConfigNames.iterator().next())).equals(IBuildConfiguration.DEFAULT_CONFIG_NAME)) + configNames = EMPTY_STRING_ARRAY; + else + configNames = buildConfigNames.toArray(new String[buildConfigNames.size()]); + } + + // Remove references for deleted buildConfigs + boolean modified = dynamicConfigRefs.keySet().retainAll(buildConfigNames); + if (modified) + clearCachedDynamicReferences(null); + // Clear the cached IBuildConfiguration[] + cachedBuildConfigs = null; + } + + /** + * Sets the map of link descriptions (String name -> LinkDescription). + * Since this method is only used internally, it never creates a copy. May + * pass null if this project does not have any linked resources + */ + public void setLinkDescriptions(HashMap linkDescriptions) { + this.linkDescriptions = linkDescriptions; + } + + /** + * Sets the map of filter descriptions (String name -> LinkedList). + * Since this method is only used internally, it never creates a copy. May + * pass null if this project does not have any filtered resources + */ + public void setFilterDescriptions(HashMap> filterDescriptions) { + this.filterDescriptions = filterDescriptions; + } + + /** + * Sets the map of variable descriptions (String name -> + * VariableDescription). Since this method is only used internally, it never + * creates a copy. May pass null if this project does not have any variables + */ + public void setVariableDescriptions(HashMap variableDescriptions) { + this.variableDescriptions = variableDescriptions; + } + + /** + * Sets the description of a link. Setting to a description of null will + * remove the link from the project description. + * @return true if the description was actually changed, + * false otherwise. + * @since 3.5 returns boolean (was void before) + */ + @SuppressWarnings({"unchecked"}) + public boolean setLinkLocation(IPath path, LinkDescription description) { + HashMap tempMap = linkDescriptions; + if (description != null) { + //addition or modification + if (tempMap == null) + tempMap = new HashMap<>(10); + else + //copy on write to protect against concurrent read + tempMap = (HashMap) tempMap.clone(); + Object oldValue = tempMap.put(path, description); + if (oldValue != null && description.equals(oldValue)) { + //not actually changed anything + return false; + } + linkDescriptions = tempMap; + } else { + //removal + if (tempMap == null) + return false; + //copy on write to protect against concurrent access + HashMap newMap = (HashMap) tempMap.clone(); + Object oldValue = newMap.remove(path); + if (oldValue == null) { + //not actually changed anything + return false; + } + linkDescriptions = newMap.size() == 0 ? null : newMap; + } + return true; + } + + /** + * Add the description of a filter. Setting to a description of null will + * remove the filter from the project description. + */ + synchronized public void addFilter(IPath path, FilterDescription description) { + Assert.isNotNull(description); + if (filterDescriptions == null) + filterDescriptions = new HashMap<>(10); + LinkedList descList = filterDescriptions.get(path); + if (descList == null) { + descList = new LinkedList<>(); + filterDescriptions.put(path, descList); + } + descList.add(description); + } + + /** + * Add the description of a filter. Setting to a description of null will + * remove the filter from the project description. + */ + synchronized public void removeFilter(IPath path, FilterDescription description) { + if (filterDescriptions != null) { + LinkedList descList = filterDescriptions.get(path); + if (descList != null) { + descList.remove(description); + if (descList.size() == 0) { + filterDescriptions.remove(path); + if (filterDescriptions.size() == 0) + filterDescriptions = null; + } + } + } + } + + /** + * Sets the description of a variable. Setting to a description of null will + * remove the variable from the project description. + * @return true if the description was actually changed, + * false otherwise. + * @since 3.5 + */ + @SuppressWarnings({"unchecked"}) + public boolean setVariableDescription(String name, VariableDescription description) { + HashMap tempMap = variableDescriptions; + if (description != null) { + // addition or modification + if (tempMap == null) + tempMap = new HashMap<>(10); + else + // copy on write to protect against concurrent read + tempMap = (HashMap) tempMap.clone(); + Object oldValue = tempMap.put(name, description); + if (oldValue != null && description.equals(oldValue)) { + //not actually changed anything + return false; + } + variableDescriptions = tempMap; + } else { + // removal + if (tempMap == null) + return false; + // copy on write to protect against concurrent access + HashMap newMap = (HashMap) tempMap.clone(); + Object oldValue = newMap.remove(name); + if (oldValue == null) { + //not actually changed anything + return false; + } + variableDescriptions = newMap.size() == 0 ? null : newMap; + } + return true; + } + + /** + * set the filters for a given resource. Setting to a description of null will + * remove the filter from the project description. + * @return true if the description was actually changed, + * false otherwise. + */ + synchronized public boolean setFilters(IPath path, LinkedList descriptions) { + if (descriptions != null) { + // addition + if (filterDescriptions == null) + filterDescriptions = new HashMap<>(10); + Object oldValue = filterDescriptions.put(path, descriptions); + if (oldValue != null && descriptions.equals(oldValue)) { + //not actually changed anything + return false; + } + } else { + // removal + if (filterDescriptions == null) + return false; + + Object oldValue = filterDescriptions.remove(path); + if (oldValue == null) { + //not actually changed anything + return false; + } + if (filterDescriptions.size() == 0) + filterDescriptions = null; + } + return true; + } + + @Override + public void setLocation(IPath path) { + this.location = path == null ? null : URIUtil.toURI(path); + } + + @Override + public void setLocationURI(URI location) { + this.location = location; + } + + @Override + public void setName(String value) { + super.setName(value); + } + + @Override + public void setNatureIds(String[] value) { + natures = value.clone(); + } + + @Override + public void setReferencedProjects(IProject[] value) { + Assert.isLegal(value != null); + staticRefs = copyAndRemoveDuplicates(value); + clearCachedDynamicReferences(null); + } + + /** + * Sets the location URI for a project snapshot that may be + * loaded automatically when the project is created in a workspace. + *

                  + * EXPERIMENTAL. This method has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. Please do not use this API without + * consulting with the Platform Core team. + *

                  + * @param snapshotLocation the location URI or + * null to clear the setting + * @see IProject#loadSnapshot(int, URI, IProgressMonitor) + * @see #getSnapshotLocationURI() + * @since 3.6 + */ + public void setSnapshotLocationURI(URI snapshotLocation) { + this.snapshotLocation = snapshotLocation; + } + + public URI getGroupLocationURI(IPath projectRelativePath) { + return LinkDescription.VIRTUAL_LOCATION; + } + + /** + * Updates the dynamic build configuration and reference state to that of the passed in + * description. + * Copies in: + *
                    + *
                  • Active configuration name
                  • + *
                  • Dynamic Project References
                  • + *
                  • Build configurations list
                  • + *
                  • Build Configuration References
                  • + *
                  + * @param description Project description to copy dynamic state from + * @return boolean indicating if anything changed requing re-calculation of WS build order + */ + public boolean updateDynamicState(ProjectDescription description) { + boolean changed = false; + if (!activeConfiguration.equals(description.activeConfiguration)) { + changed = true; + activeConfiguration = description.activeConfiguration; + } + if (!Arrays.equals(dynamicRefs, description.dynamicRefs)) { + changed = true; + setDynamicReferences(description.dynamicRefs); + } + if (!Arrays.equals(configNames, description.configNames)) { + changed = true; + setBuildConfigs(description.configNames); + } + if (configRefsHaveChanges(dynamicConfigRefs, description.dynamicConfigRefs)) { + changed = true; + dynamicConfigRefs = new HashMap<>(description.dynamicConfigRefs); + } + if (changed) + clearCachedDynamicReferences(null); + return changed; + } + + /** + * Computes the dynamic references for the given project + configuration. + */ + private static IProject[] computeDynamicReferencesForProject(IBuildConfiguration buildConfig, ICommand[] buildSpec) { + List result = new ArrayList<>(); + for (ICommand command : buildSpec) { + IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_BUILDERS, command.getBuilderName()); + + if (extension == null) { + continue; + } + + IConfigurationElement[] configurationElements = extension.getConfigurationElements(); + + if (configurationElements.length == 0) { + continue; + } + + IConfigurationElement element = configurationElements[0]; + + Object executableExtension; + try { + IConfigurationElement[] children = element.getChildren("dynamicReference"); //$NON-NLS-1$ + if (children.length != 0) { + executableExtension = children[0].createExecutableExtension("class"); //$NON-NLS-1$ + if (executableExtension instanceof IDynamicReferenceProvider) { + IDynamicReferenceProvider provider = (IDynamicReferenceProvider) executableExtension; + + result.addAll(provider.getDependentProjects(buildConfig)); + } + } + } catch (CoreException e) { + String problemElement = element.toString(); + ResourcesPlugin.getPlugin().getLog().log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, "Unable to load dynamic reference provider: " + problemElement, e)); //$NON-NLS-1$ + } + } + return result.toArray(new IProject[0]); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java new file mode 100644 index 0000000000..ccf469c0ed --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectDescriptionReader.java @@ -0,0 +1,1119 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support + * Markus Schorn (Wind River) - [306575] Save snapshot location with project + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import javax.xml.parsers.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.internal.localstore.SafeFileInputStream; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; +import org.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Reads serialized project descriptions. + * + * Note: Suppress warnings on whole class because of unusual use of objectStack. + */ +@SuppressWarnings({"unchecked"}) +public class ProjectDescriptionReader extends DefaultHandler implements IModelObjectConstants { + + //states + protected static final int S_BUILD_COMMAND = 0; + protected static final int S_BUILD_COMMAND_ARGUMENTS = 1; + protected static final int S_BUILD_COMMAND_NAME = 2; + protected static final int S_BUILD_COMMAND_TRIGGERS = 3; + protected static final int S_BUILD_SPEC = 4; + protected static final int S_DICTIONARY = 5; + protected static final int S_DICTIONARY_KEY = 6; + protected static final int S_DICTIONARY_VALUE = 7; + protected static final int S_INITIAL = 8; + protected static final int S_LINK = 9; + protected static final int S_LINK_LOCATION = 10; + protected static final int S_LINK_LOCATION_URI = 11; + protected static final int S_LINK_PATH = 12; + protected static final int S_LINK_TYPE = 13; + protected static final int S_LINKED_RESOURCES = 14; + protected static final int S_NATURE_NAME = 15; + protected static final int S_NATURES = 16; + protected static final int S_PROJECT_COMMENT = 17; + protected static final int S_PROJECT_DESC = 18; + protected static final int S_PROJECT_NAME = 19; + protected static final int S_PROJECTS = 20; + protected static final int S_REFERENCED_PROJECT_NAME = 21; + + protected static final int S_FILTERED_RESOURCES = 23; + protected static final int S_FILTER = 24; + protected static final int S_FILTER_ID = 25; + protected static final int S_FILTER_PATH = 26; + protected static final int S_FILTER_TYPE = 27; + + protected static final int S_MATCHER = 28; + protected static final int S_MATCHER_ID = 29; + protected static final int S_MATCHER_ARGUMENTS = 30; + + protected static final int S_VARIABLE_LIST = 31; + protected static final int S_VARIABLE = 32; + protected static final int S_VARIABLE_NAME = 33; + protected static final int S_VARIABLE_VALUE = 34; + + protected static final int S_SNAPSHOT_LOCATION = 35; + + /** + * Singleton sax parser factory + */ + private static SAXParserFactory singletonParserFactory; + + /** + * Singleton sax parser + */ + private static SAXParser singletonParser; + + protected final StringBuilder charBuffer = new StringBuilder(); + + protected Stack objectStack; + protected MultiStatus problems; + + /** + * The project we are reading the description for, or null if unknown. + */ + private final IProject project; + // The project description we are creating. + ProjectDescription projectDescription = null; + + protected int state = S_INITIAL; + + /** + * Returns the SAXParser to use when parsing project description files. + * @throws ParserConfigurationException + * @throws SAXException + */ + private static synchronized SAXParser createParser() throws ParserConfigurationException, SAXException { + //the parser can't be used concurrently, so only use singleton when workspace is locked + if (!isWorkspaceLocked()) + return createParserFactory().newSAXParser(); + if (singletonParser == null) { + singletonParser = createParserFactory().newSAXParser(); + } + return singletonParser; + } + + /** + * Returns the SAXParserFactory to use when parsing project description files. + * @throws ParserConfigurationException + */ + private static synchronized SAXParserFactory createParserFactory() throws ParserConfigurationException { + if (singletonParserFactory == null) { + singletonParserFactory = SAXParserFactory.newInstance(); + singletonParserFactory.setNamespaceAware(true); + try { + singletonParserFactory.setFeature("http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$ + } catch (SAXException e) { + // In case support for this feature is removed + } + } + return singletonParserFactory; + } + + private static boolean isWorkspaceLocked() { + try { + return ((Workspace) ResourcesPlugin.getWorkspace()).getWorkManager().isLockAlreadyAcquired(); + } catch (CoreException e) { + return false; + } + } + + public ProjectDescriptionReader() { + this.project = null; + } + + public ProjectDescriptionReader(IProject project) { + this.project = project; + } + + /** + * @see ContentHandler#characters(char[], int, int) + */ + @Override + public void characters(char[] chars, int offset, int length) { + //accumulate characters and process them when endElement is reached + charBuffer.append(chars, offset, length); + } + + /** + * End of an element that is part of a build command + */ + private void endBuildCommandElement(String elementName) { + if (elementName.equals(BUILD_COMMAND)) { + // Pop this BuildCommand off the stack. + BuildCommand command = (BuildCommand) objectStack.pop(); + // Add this BuildCommand to a array list of BuildCommands. + ArrayList commandList = (ArrayList) objectStack.peek(); + commandList.add(command); + state = S_BUILD_SPEC; + } + } + + /** + * End of an element that is part of a build spec + */ + private void endBuildSpecElement(String elementName) { + if (elementName.equals(BUILD_SPEC)) { + // Pop off the array list of BuildCommands and add them to the + // ProjectDescription which is the next thing on the stack. + ArrayList commands = (ArrayList) objectStack.pop(); + state = S_PROJECT_DESC; + if (commands.isEmpty()) + return; + ICommand[] commandArray = commands.toArray(new ICommand[commands.size()]); + projectDescription.setBuildSpec(commandArray); + } + } + + /** + * End a build triggers element and set the triggers for the current + * build command element. + */ + private void endBuildTriggersElement(String elementName) { + if (elementName.equals(BUILD_TRIGGERS)) { + state = S_BUILD_COMMAND; + BuildCommand command = (BuildCommand) objectStack.peek(); + //presence of this element indicates the builder is configurable + command.setConfigurable(true); + //clear all existing values + command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, false); + command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, false); + + //set new values according to value in the triggers element + StringTokenizer tokens = new StringTokenizer(charBuffer.toString(), ","); //$NON-NLS-1$ + while (tokens.hasMoreTokens()) { + String next = tokens.nextToken(); + if (next.toLowerCase().equals(TRIGGER_AUTO)) { + command.setBuilding(IncrementalProjectBuilder.AUTO_BUILD, true); + } else if (next.toLowerCase().equals(TRIGGER_CLEAN)) { + command.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, true); + } else if (next.toLowerCase().equals(TRIGGER_FULL)) { + command.setBuilding(IncrementalProjectBuilder.FULL_BUILD, true); + } else if (next.toLowerCase().equals(TRIGGER_INCREMENTAL)) { + command.setBuilding(IncrementalProjectBuilder.INCREMENTAL_BUILD, true); + } + } + } + } + + /** + * End of a dictionary element + */ + private void endDictionary(String elementName) { + if (elementName.equals(DICTIONARY)) { + // Pick up the value and then key off the stack and add them + // to the HashMap which is just below them on the stack. + // Leave the HashMap on the stack to pick up more key/value + // pairs if they exist. + String value = (String) objectStack.pop(); + String key = (String) objectStack.pop(); + ((HashMap) objectStack.peek()).put(key, value); + state = S_BUILD_COMMAND_ARGUMENTS; + } + } + + private void endDictionaryKey(String elementName) { + if (elementName.equals(KEY)) { + // There is a value place holder on the top of the stack and + // a key place holder just below it. + String value = (String) objectStack.pop(); + String oldKey = (String) objectStack.pop(); + String newKey = charBuffer.toString(); + if (oldKey != null && oldKey.length() != 0) { + parseProblem(NLS.bind(Messages.projRead_whichKey, oldKey, newKey)); + objectStack.push(oldKey); + } else { + objectStack.push(newKey); + } + //push back the dictionary value + objectStack.push(value); + state = S_DICTIONARY; + } + } + + private void endDictionaryValue(String elementName) { + if (elementName.equals(VALUE)) { + String newValue = charBuffer.toString(); + // There is a value place holder on the top of the stack + String oldValue = (String) objectStack.pop(); + if (oldValue != null && oldValue.length() != 0) { + parseProblem(NLS.bind(Messages.projRead_whichValue, oldValue, newValue)); + objectStack.push(oldValue); + } else { + objectStack.push(newValue); + } + state = S_DICTIONARY; + } + } + + /** + * @see ContentHandler#endElement(String, String, String) + */ + @Override + public void endElement(String uri, String elementName, String qname) { + switch (state) { + case S_PROJECT_DESC : + // Don't think we need to do anything here. + break; + case S_PROJECT_NAME : + if (elementName.equals(NAME)) { + // Project names cannot have leading/trailing whitespace + // as they are IResource names. + projectDescription.setName(charBuffer.toString().trim()); + state = S_PROJECT_DESC; + } + break; + case S_PROJECTS : + if (elementName.equals(PROJECTS)) { + endProjectsElement(elementName); + state = S_PROJECT_DESC; + } + break; + case S_DICTIONARY : + endDictionary(elementName); + break; + case S_BUILD_COMMAND_ARGUMENTS : + if (elementName.equals(ARGUMENTS)) { + // There is a hashmap on the top of the stack with the + // arguments (if any). + HashMap dictionaryArgs = (HashMap) objectStack.pop(); + state = S_BUILD_COMMAND; + if (dictionaryArgs.isEmpty()) + break; + // Below the hashMap on the stack, there is a BuildCommand. + ((BuildCommand) objectStack.peek()).setArguments(dictionaryArgs); + } + break; + case S_BUILD_COMMAND : + endBuildCommandElement(elementName); + break; + case S_BUILD_SPEC : + endBuildSpecElement(elementName); + break; + case S_BUILD_COMMAND_TRIGGERS : + endBuildTriggersElement(elementName); + break; + case S_NATURES : + endNaturesElement(elementName); + break; + case S_LINK : + endLinkElement(elementName); + break; + case S_LINKED_RESOURCES : + endLinkedResourcesElement(elementName); + break; + case S_VARIABLE : + endVariableElement(elementName); + break; + case S_FILTER : + endFilterElement(elementName); + break; + case S_FILTERED_RESOURCES : + endFilteredResourcesElement(elementName); + break; + case S_VARIABLE_LIST : + endVariableListElement(elementName); + break; + case S_PROJECT_COMMENT : + if (elementName.equals(COMMENT)) { + projectDescription.setComment(charBuffer.toString()); + state = S_PROJECT_DESC; + } + break; + case S_REFERENCED_PROJECT_NAME : + if (elementName.equals(PROJECT)) { + //top of stack is list of project references + // Referenced projects are just project names and, therefore, + // are also IResource names and cannot have leading/trailing + // whitespace. + ((ArrayList) objectStack.peek()).add(charBuffer.toString().trim()); + state = S_PROJECTS; + } + break; + case S_BUILD_COMMAND_NAME : + if (elementName.equals(NAME)) { + //top of stack is the build command + // A build command name is an extension id and + // cannot have leading/trailing whitespace. + ((BuildCommand) objectStack.peek()).setName(charBuffer.toString().trim()); + state = S_BUILD_COMMAND; + } + break; + case S_DICTIONARY_KEY : + endDictionaryKey(elementName); + break; + case S_DICTIONARY_VALUE : + endDictionaryValue(elementName); + break; + case S_NATURE_NAME : + if (elementName.equals(NATURE)) { + //top of stack is list of nature names + // A nature name is an extension id and cannot + // have leading/trailing whitespace. + ((ArrayList) objectStack.peek()).add(charBuffer.toString().trim()); + state = S_NATURES; + } + break; + case S_LINK_PATH : + endLinkPath(elementName); + break; + case S_LINK_TYPE : + endLinkType(elementName); + break; + case S_LINK_LOCATION : + endLinkLocation(elementName); + break; + case S_LINK_LOCATION_URI : + endLinkLocationURI(elementName); + break; + case S_FILTER_ID : + endFilterId(elementName); + break; + case S_FILTER_PATH : + endFilterPath(elementName); + break; + case S_FILTER_TYPE : + endFilterType(elementName); + break; + case S_MATCHER : + endMatcherElement(elementName); + break; + case S_MATCHER_ID : + endMatcherID(elementName); + break; + case S_MATCHER_ARGUMENTS : + endMatcherArguments(elementName); + break; + case S_VARIABLE_NAME : + endVariableName(elementName); + break; + case S_VARIABLE_VALUE : + endVariableValue(elementName); + break; + case S_SNAPSHOT_LOCATION : + endSnapshotLocation(elementName); + break; + } + charBuffer.setLength(0); + } + + /** + * End this group of linked resources and add them to the project description. + */ + private void endLinkedResourcesElement(String elementName) { + if (elementName.equals(LINKED_RESOURCES)) { + HashMap linkedResources = (HashMap) objectStack.pop(); + state = S_PROJECT_DESC; + if (linkedResources.isEmpty()) + return; + projectDescription.setLinkDescriptions(linkedResources); + } + } + + /** + * End this group of linked resources and add them to the project description. + */ + private void endFilteredResourcesElement(String elementName) { + if (elementName.equals(FILTERED_RESOURCES)) { + HashMap> filteredResources = (HashMap>) objectStack.pop(); + state = S_PROJECT_DESC; + if (filteredResources.isEmpty()) + return; + projectDescription.setFilterDescriptions(filteredResources); + } + } + + /** + * End this group of group resources and add them to the project + * description. + */ + private void endVariableListElement(String elementName) { + if (elementName.equals(VARIABLE_LIST)) { + HashMap variableList = (HashMap) objectStack.pop(); + state = S_PROJECT_DESC; + if (variableList.isEmpty()) + return; + projectDescription.setVariableDescriptions(variableList); + } + } + + /** + * End a single linked resource and add it to the HashMap. + */ + private void endLinkElement(String elementName) { + if (elementName.equals(LINK)) { + state = S_LINKED_RESOURCES; + // Pop off the link description + LinkDescription link = (LinkDescription) objectStack.pop(); + // Make sure that you have something reasonable + IPath path = link.getProjectRelativePath(); + int type = link.getType(); + URI location = link.getLocationURI(); + if (location == null) { + parseProblem(NLS.bind(Messages.projRead_badLinkLocation, path, Integer.toString(type))); + return; + } + if ((path == null) || path.segmentCount() == 0) { + parseProblem(NLS.bind(Messages.projRead_emptyLinkName, Integer.toString(type), location)); + return; + } + if (type == -1) { + parseProblem(NLS.bind(Messages.projRead_badLinkType, path, location)); + return; + } + + // The HashMap of linked resources is the next thing on the stack + ((HashMap) objectStack.peek()).put(link.getProjectRelativePath(), link); + } + } + + private void endMatcherElement(String elementName) { + if (elementName.equals(MATCHER)) { + // Pop off an array (Object[2]) containing the matcher id and arguments. + Object[] matcher = (Object[]) objectStack.pop(); + // Make sure that you have something reasonable + String id = (String) matcher[0]; + // the id can't be null + if (id == null) { + parseProblem(NLS.bind(Messages.projRead_badFilterID, id)); + return; + } + + if (objectStack.peek() instanceof ArrayList) { + state = S_MATCHER_ARGUMENTS; + // The ArrayList of matchers is the next thing on the stack + ArrayList list = ((ArrayList) objectStack.peek()); + list.add(new FileInfoMatcherDescription((String) matcher[0], matcher[1])); + } + + if (objectStack.peek() instanceof FilterDescription) { + state = S_FILTER; + FilterDescription d = ((FilterDescription) objectStack.peek()); + d.setFileInfoMatcherDescription(new FileInfoMatcherDescription((String) matcher[0], matcher[1])); + } + } + } + + /** + * End a single filtered resource and add it to the HashMap. + */ + private void endFilterElement(String elementName) { + if (elementName.equals(FILTER)) { + // Pop off the filter description + FilterDescription filter = (FilterDescription) objectStack.pop(); + if (project != null) { + // Make sure that you have something reasonable + IPath path = filter.getResource().getProjectRelativePath(); + int type = filter.getType(); + // arguments can be null + if (path == null) { + parseProblem(NLS.bind(Messages.projRead_emptyFilterName, Integer.toString(type))); + return; + } + if (type == -1) { + parseProblem(NLS.bind(Messages.projRead_badFilterType, path)); + return; + } + + // The HashMap of filtered resources is the next thing on the stack + HashMap> map = ((HashMap>) objectStack.peek()); + LinkedList list = map.get(filter.getResource().getProjectRelativePath()); + if (list == null) { + list = new LinkedList<>(); + map.put(filter.getResource().getProjectRelativePath(), list); + } + list.add(filter); + } else { + // if the project is null, that means that we're loading a project description to retrieve + // some meta data only. + String key = ""; // an empty key; //$NON-NLS-1$ + HashMap> map = ((HashMap>) objectStack.peek()); + LinkedList list = map.get(key); + if (list == null) { + list = new LinkedList<>(); + map.put(key, list); + } + list.add(filter); + } + state = S_FILTERED_RESOURCES; + } + } + + /** + * End a single group resource and add it to the HashMap. + */ + private void endVariableElement(String elementName) { + if (elementName.equals(VARIABLE)) { + state = S_VARIABLE_LIST; + // Pop off the link description + VariableDescription desc = (VariableDescription) objectStack.pop(); + // Make sure that you have something reasonable + if (desc.getName().length() == 0) { + parseProblem(NLS.bind(Messages.projRead_emptyVariableName, project.getName())); + return; + } + + // The HashMap of variables is the next thing on the stack + ((HashMap) objectStack.peek()).put(desc.getName(), desc); + } + } + + /** + * For backwards compatibility, link locations in the local file system are represented + * in the project description under the "location" tag. + * @param elementName + */ + private void endLinkLocation(String elementName) { + if (elementName.equals(LOCATION)) { + // A link location is an URI. URIs cannot have leading/trailing whitespace + String newLocation = charBuffer.toString().trim(); + // objectStack has a LinkDescription on it. Set the type on this LinkDescription. + URI oldLocation = ((LinkDescription) objectStack.peek()).getLocationURI(); + if (oldLocation != null) { + parseProblem(NLS.bind(Messages.projRead_badLocation, oldLocation, newLocation)); + } else { + ((LinkDescription) objectStack.peek()).setLocationURI(URIUtil.toURI(Path.fromPortableString(newLocation))); + } + state = S_LINK; + } + } + + /** + * Link locations that are not stored in the local file system are represented + * in the project description under the "locationURI" tag. + * @param elementName + */ + private void endLinkLocationURI(String elementName) { + if (elementName.equals(LOCATION_URI)) { + // A link location is an URI. URIs cannot have leading/trailing whitespace + String newLocation = charBuffer.toString().trim(); + // objectStack has a LinkDescription on it. Set the type on this LinkDescription. + URI oldLocation = ((LinkDescription) objectStack.peek()).getLocationURI(); + if (oldLocation != null) { + parseProblem(NLS.bind(Messages.projRead_badLocation, oldLocation, newLocation)); + } else { + try { + ((LinkDescription) objectStack.peek()).setLocationURI(new URI(newLocation)); + } catch (URISyntaxException e) { + String msg = Messages.projRead_failureReadingProjectDesc; + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, msg, e)); + } + } + state = S_LINK; + } + } + + private void endLinkPath(String elementName) { + if (elementName.equals(NAME)) { + IPath newPath = new Path(charBuffer.toString()); + // objectStack has a LinkDescription on it. Set the name + // on this LinkDescription. + IPath oldPath = ((LinkDescription) objectStack.peek()).getProjectRelativePath(); + if (oldPath.segmentCount() != 0) { + parseProblem(NLS.bind(Messages.projRead_badLinkName, oldPath, newPath)); + } else { + ((LinkDescription) objectStack.peek()).setPath(newPath); + } + state = S_LINK; + } + } + + private void endMatcherID(String elementName) { + if (elementName.equals(ID)) { + // The matcher id is String. + String newID = charBuffer.toString().trim(); + // objectStack has an array (Object[2]) on it for the matcher id and arguments. + String oldID = (String) ((Object[]) objectStack.peek())[0]; + if (oldID != null) { + parseProblem(NLS.bind(Messages.projRead_badID, oldID, newID)); + } else { + ((Object[]) objectStack.peek())[0] = newID; + } + state = S_MATCHER; + } + } + + private void endMatcherArguments(String elementName) { + if (elementName.equals(ARGUMENTS)) { + ArrayList matchers = (ArrayList) objectStack.pop(); + Object newArguments = charBuffer.toString(); + + if (matchers.size() > 0) + newArguments = matchers.toArray(new FileInfoMatcherDescription[matchers.size()]); + + // objectStack has an array (Object[2]) on it for the matcher id and arguments. + String oldArguments = (String) ((Object[]) objectStack.peek())[1]; + if (oldArguments != null) { + parseProblem(NLS.bind(Messages.projRead_badArguments, oldArguments, newArguments)); + } else + ((Object[]) objectStack.peek())[1] = newArguments; + state = S_MATCHER; + } + } + + private void endFilterId(String elementName) { + if (elementName.equals(ID)) { + Long newId = Long.parseLong(charBuffer.toString()); + // objectStack has a FilterDescription on it. Set the name + // on this FilterDescription. + long oldId = ((FilterDescription) objectStack.peek()).getId(); + if (oldId != 0) { + parseProblem(NLS.bind(Messages.projRead_badFilterName, oldId, newId)); + } else { + ((FilterDescription) objectStack.peek()).setId(newId.longValue()); + } + state = S_FILTER; + } + } + + private void endFilterPath(String elementName) { + if (elementName.equals(NAME)) { + IPath newPath = new Path(charBuffer.toString()); + // objectStack has a FilterDescription on it. Set the name + // on this FilterDescription. + IResource oldResource = ((FilterDescription) objectStack.peek()).getResource(); + if (oldResource != null) { + parseProblem(NLS.bind(Messages.projRead_badFilterName, oldResource.getProjectRelativePath(), newPath)); + } else { + if (project != null) { + ((FilterDescription) objectStack.peek()).setResource(newPath.isEmpty() ? (IResource) project : project.getFolder(newPath)); + } else { + // if the project is null, that means that we're loading a project description to retrieve + // some meta data only. + ((FilterDescription) objectStack.peek()).setResource(null); + } + } + state = S_FILTER; + } + } + + private void endFilterType(String elementName) { + if (elementName.equals(TYPE)) { + int newType = -1; + try { + // parseInt expects a string containing only numerics + // or a leading '-'. Ensure there is no leading/trailing + // whitespace. + newType = Integer.parseInt(charBuffer.toString().trim()); + } catch (NumberFormatException e) { + log(e); + } + // objectStack has a FilterDescription on it. Set the type + // on this FilterDescription. + int oldType = ((FilterDescription) objectStack.peek()).getType(); + if (oldType != -1) { + parseProblem(NLS.bind(Messages.projRead_badFilterType2, Integer.toString(oldType), Integer.toString(newType))); + } else { + ((FilterDescription) objectStack.peek()).setType(newType); + } + state = S_FILTER; + } + } + + private void endVariableName(String elementName) { + if (elementName.equals(NAME)) { + String value = charBuffer.toString(); + // objectStack has a VariableDescription on it. Set the value + // on this ValueDescription. + ((VariableDescription) objectStack.peek()).setName(value); + state = S_VARIABLE; + } + } + + private void endVariableValue(String elementName) { + if (elementName.equals(VALUE)) { + String value = charBuffer.toString(); + // objectStack has a VariableDescription on it. Set the value + // on this ValueDescription. + ((VariableDescription) objectStack.peek()).setValue(value); + state = S_VARIABLE; + } + } + + private void endLinkType(String elementName) { + if (elementName.equals(TYPE)) { + //FIXME we should handle this case by removing the entire link + //for now we default to a file link + int newType = IResource.FILE; + try { + // parseInt expects a string containing only numerics + // or a leading '-'. Ensure there is no leading/trailing + // whitespace. + newType = Integer.parseInt(charBuffer.toString().trim()); + } catch (NumberFormatException e) { + log(e); + } + // objectStack has a LinkDescription on it. Set the type + // on this LinkDescription. + int oldType = ((LinkDescription) objectStack.peek()).getType(); + if (oldType != -1) { + parseProblem(NLS.bind(Messages.projRead_badLinkType2, Integer.toString(oldType), Integer.toString(newType))); + } else { + ((LinkDescription) objectStack.peek()).setType(newType); + } + state = S_LINK; + } + } + + /** + * End of an element that is part of a nature list + */ + private void endNaturesElement(String elementName) { + if (elementName.equals(NATURES)) { + // Pop the array list of natures off the stack + ArrayList natures = (ArrayList) objectStack.pop(); + state = S_PROJECT_DESC; + if (natures.size() == 0) + return; + String[] natureNames = natures.toArray(new String[natures.size()]); + projectDescription.setNatureIds(natureNames); + } + } + + /** + * End of an element that is part of a project references list + */ + private void endProjectsElement(String elementName) { + // Pop the array list that contains all the referenced project names + ArrayList referencedProjects = (ArrayList) objectStack.pop(); + if (referencedProjects.size() == 0) + // Don't bother adding an empty group of referenced projects to the + // project descriptor. + return; + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IProject[] projects = new IProject[referencedProjects.size()]; + for (int i = 0; i < projects.length; i++) { + projects[i] = root.getProject(referencedProjects.get(i)); + } + projectDescription.setReferencedProjects(projects); + } + + private void endSnapshotLocation(String elementName) { + if (elementName.equals(SNAPSHOT_LOCATION)) { + String location = charBuffer.toString().trim(); + try { + projectDescription.setSnapshotLocationURI(new URI(location)); + } catch (URISyntaxException e) { + String msg = NLS.bind(Messages.projRead_badSnapshotLocation, location); + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, msg, e)); + } + state = S_PROJECT_DESC; + } + } + + /** + * @see ErrorHandler#error(SAXParseException) + */ + @Override + public void error(SAXParseException error) { + log(error); + } + + /** + * @see ErrorHandler#fatalError(SAXParseException) + */ + @Override + public void fatalError(SAXParseException error) throws SAXException { + // ensure a null value is not passed as message to Status constructor (bug 42782) + String message = error.getMessage(); + if (project != null) + message = NLS.bind(Messages.resources_readMeta, project.getName()); + problems.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message == null ? "" : message, error)); //$NON-NLS-1$ + throw error; + } + + protected void log(Exception ex) { + String message = ex.getMessage(); + if (project != null) + message = NLS.bind(Messages.resources_readMeta, project.getName()); + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message == null ? "" : message, ex)); //$NON-NLS-1$ + } + + private void parseProblem(String errorMessage) { + problems.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, errorMessage, null)); + } + + private void parseProjectDescription(String elementName) { + if (elementName.equals(NAME)) { + state = S_PROJECT_NAME; + return; + } + if (elementName.equals(COMMENT)) { + state = S_PROJECT_COMMENT; + return; + } + if (elementName.equals(PROJECTS)) { + state = S_PROJECTS; + // Push an array list on the object stack to hold the name + // of all the referenced projects. This array list will be + // popped off the stack, massaged into the right format + // and added to the project description when we hit the + // end element for PROJECTS. + objectStack.push(new ArrayList()); + return; + } + if (elementName.equals(BUILD_SPEC)) { + state = S_BUILD_SPEC; + // Push an array list on the object stack to hold the build commands + // for this build spec. This array list will be popped off the stack, + // massaged into the right format and added to the project's build + // spec when we hit the end element for BUILD_SPEC. + objectStack.push(new ArrayList()); + return; + } + if (elementName.equals(NATURES)) { + state = S_NATURES; + // Push an array list to hold all the nature names. + objectStack.push(new ArrayList()); + return; + } + if (elementName.equals(LINKED_RESOURCES)) { + // Push a HashMap to collect all the links. + objectStack.push(new HashMap()); + state = S_LINKED_RESOURCES; + return; + } + if (elementName.equals(FILTERED_RESOURCES)) { + // Push a HashMap to collect all the filters. + objectStack.push(new HashMap>()); + state = S_FILTERED_RESOURCES; + return; + } + if (elementName.equals(VARIABLE_LIST)) { + // Push a HashMap to collect all the variables. + objectStack.push(new HashMap()); + state = S_VARIABLE_LIST; + return; + } + if (elementName.equals(SNAPSHOT_LOCATION)) { + state = S_SNAPSHOT_LOCATION; + return; + } + } + + public ProjectDescription read(InputSource input) { + problems = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, Messages.projRead_failureReadingProjectDesc, null); + objectStack = new Stack<>(); + state = S_INITIAL; + try { + createParser().parse(input, this); + } catch (ParserConfigurationException e) { + log(e); + } catch (IOException e) { + log(e); + } catch (SAXException e) { + log(e); + } + + if (projectDescription != null && projectDescription.getName() == null) + parseProblem(Messages.projRead_missingProjectName); + + switch (problems.getSeverity()) { + case IStatus.ERROR : + Policy.log(problems); + return null; + case IStatus.WARNING : + case IStatus.INFO : + Policy.log(problems); + case IStatus.OK : + default : + return projectDescription; + } + } + + /** + * Reads and returns a project description stored at the given location + */ + public ProjectDescription read(IPath location) throws IOException { + BufferedInputStream file = null; + try { + file = new BufferedInputStream(new FileInputStream(location.toFile())); + return read(new InputSource(file)); + } finally { + FileUtil.safeClose(file); + } + } + + /** + * Reads and returns a project description stored at the given location, or + * temporary location. + */ + public ProjectDescription read(IPath location, IPath tempLocation) throws IOException { + SafeFileInputStream file = new SafeFileInputStream(location.toOSString(), tempLocation.toOSString()); + try { + return read(new InputSource(file)); + } finally { + file.close(); + } + } + + /** + * @see ContentHandler#startElement(String, String, String, Attributes) + */ + @Override + public void startElement(String uri, String elementName, String qname, Attributes attributes) throws SAXException { + //clear the character buffer at the start of every element + charBuffer.setLength(0); + switch (state) { + case S_INITIAL : + if (elementName.equals(PROJECT_DESCRIPTION)) { + state = S_PROJECT_DESC; + projectDescription = new ProjectDescription(); + } else { + throw (new SAXException(NLS.bind(Messages.projRead_notProjectDescription, elementName))); + } + break; + case S_PROJECT_DESC : + parseProjectDescription(elementName); + break; + case S_PROJECTS : + if (elementName.equals(PROJECT)) { + state = S_REFERENCED_PROJECT_NAME; + } + break; + case S_BUILD_SPEC : + if (elementName.equals(BUILD_COMMAND)) { + state = S_BUILD_COMMAND; + objectStack.push(new BuildCommand()); + } + break; + case S_BUILD_COMMAND : + if (elementName.equals(NAME)) { + state = S_BUILD_COMMAND_NAME; + } else if (elementName.equals(BUILD_TRIGGERS)) { + state = S_BUILD_COMMAND_TRIGGERS; + } else if (elementName.equals(ARGUMENTS)) { + state = S_BUILD_COMMAND_ARGUMENTS; + // Push a HashMap to hold all the key/value pairs which + // will become the argument list. + objectStack.push(new HashMap()); + } + break; + case S_BUILD_COMMAND_ARGUMENTS : + if (elementName.equals(DICTIONARY)) { + state = S_DICTIONARY; + // Push 2 strings for the key/value pair to be read + objectStack.push(""); // key //$NON-NLS-1$ + objectStack.push(""); // value //$NON-NLS-1$ + } + break; + case S_DICTIONARY : + if (elementName.equals(KEY)) { + state = S_DICTIONARY_KEY; + } else if (elementName.equals(VALUE)) { + state = S_DICTIONARY_VALUE; + } + break; + case S_NATURES : + if (elementName.equals(NATURE)) { + state = S_NATURE_NAME; + } + break; + case S_LINKED_RESOURCES : + if (elementName.equals(LINK)) { + state = S_LINK; + // Push place holders for the name, type and location of + // this link. + objectStack.push(new LinkDescription()); + } + break; + case S_VARIABLE_LIST : + if (elementName.equals(VARIABLE)) { + state = S_VARIABLE; + // Push place holders for the name, type and location of + // this link. + objectStack.push(new VariableDescription()); + } + break; + case S_LINK : + if (elementName.equals(NAME)) { + state = S_LINK_PATH; + } else if (elementName.equals(TYPE)) { + state = S_LINK_TYPE; + } else if (elementName.equals(LOCATION)) { + state = S_LINK_LOCATION; + } else if (elementName.equals(LOCATION_URI)) { + state = S_LINK_LOCATION_URI; + } + break; + case S_FILTERED_RESOURCES : + if (elementName.equals(FILTER)) { + state = S_FILTER; + // Push place holders for the name, type, id and arguments of + // this filter. + objectStack.push(new FilterDescription()); + } + break; + case S_FILTER : + if (elementName.equals(ID)) { + state = S_FILTER_ID; + } else if (elementName.equals(NAME)) { + state = S_FILTER_PATH; + } else if (elementName.equals(TYPE)) { + state = S_FILTER_TYPE; + } else if (elementName.equals(MATCHER)) { + state = S_MATCHER; + // Push an array for the matcher id and arguments + objectStack.push(new Object[2]); + } + break; + case S_MATCHER : + if (elementName.equals(ID)) { + state = S_MATCHER_ID; + } else if (elementName.equals(ARGUMENTS)) { + state = S_MATCHER_ARGUMENTS; + objectStack.push(new ArrayList()); + } + break; + case S_MATCHER_ARGUMENTS : + if (elementName.equals(MATCHER)) { + state = S_MATCHER; + // Push an array for the matcher id and arguments + objectStack.push(new Object[2]); + } + break; + case S_VARIABLE : + if (elementName.equals(NAME)) { + state = S_VARIABLE_NAME; + } else if (elementName.equals(VALUE)) { + state = S_VARIABLE_VALUE; + } + break; + } + } + + /** + * @see ErrorHandler#warning(SAXParseException) + */ + @Override + public void warning(SAXParseException error) { + log(error); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java new file mode 100644 index 0000000000..7b7151e6a8 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectInfo.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.HashMap; +import org.eclipse.core.internal.events.BuildCommand; +import org.eclipse.core.resources.ICommand; +import org.eclipse.core.resources.IProjectNature; +import org.eclipse.core.runtime.content.IContentTypeMatcher; + +public class ProjectInfo extends ResourceInfo { + + /** The description of this object */ + protected ProjectDescription description = null; + + /** The list of natures for this project */ + protected HashMap natures = null; + + /** The property store for this resource (used only by the compatibility fragment) */ + protected Object propertyStore = null; + + /** The content type matcher for this project. */ + protected IContentTypeMatcher matcher = null; + + /** + * Discards stale natures on this project after project description + * has changed. + */ + public synchronized void discardNatures() { + natures = null; + } + + /** + * Discards any stale state on this project after it has been moved. Builder + * instances must be cleared because they reference the old project handle. + */ + public synchronized void fixupAfterMove() { + natures = null; + // note that the property store instance will be recreated lazily + propertyStore = null; + if (description != null) { + ICommand[] buildSpec = description.getBuildSpec(false); + for (int i = 0; i < buildSpec.length; i++) + ((BuildCommand) buildSpec[i]).setBuilders(null); + } + } + + /** + * Returns the description associated with this info. The return value may be null. + */ + public ProjectDescription getDescription() { + return description; + } + + /** + * Returns the content type matcher associated with this info. The return value may be null. + */ + public IContentTypeMatcher getMatcher() { + return matcher; + } + + public IProjectNature getNature(String natureId) { + // thread safety: (Concurrency001) + HashMap temp = natures; + if (temp == null) + return null; + return temp.get(natureId); + } + + /** + * Returns the property store associated with this info. The return value may be null. + */ + @Override + public Object getPropertyStore() { + return propertyStore; + } + + /** + * Sets the description associated with this info. The value may be null. + */ + public void setDescription(ProjectDescription value) { + if (description != null) { + //if we already have a description, assign the new + //build spec on top of the old one to ensure we maintain + //any existing builder instances in the old build commands + ICommand[] oldSpec = description.buildSpec; + ICommand[] newSpec = value.buildSpec; + value.buildSpec = oldSpec; + value.setBuildSpec(newSpec); + } + description = value; + } + + /** + * Sets the content type matcher to be associated with this info. The value may be null. + */ + public void setMatcher(IContentTypeMatcher matcher) { + this.matcher = matcher; + } + + @SuppressWarnings({"unchecked"}) + public synchronized void setNature(String natureId, IProjectNature value) { + // thread safety: (Concurrency001) + if (value == null) { + if (natures == null) + return; + HashMap temp = (HashMap) natures.clone(); + temp.remove(natureId); + if (temp.isEmpty()) + natures = null; + else + natures = temp; + } else { + HashMap temp = natures; + if (temp == null) + temp = new HashMap<>(5); + else + temp = (HashMap) natures.clone(); + temp.put(natureId, value); + natures = temp; + } + } + + /** + * Sets the property store associated with this info. The value may be null. + */ + @Override + public void setPropertyStore(Object value) { + propertyStore = value; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java new file mode 100644 index 0000000000..662a72f616 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectNatureDescriptor.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.ArrayList; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IProjectNatureDescriptor; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + */ +public class ProjectNatureDescriptor implements IProjectNatureDescriptor { + protected String id; + protected String label; + protected String[] requiredNatures; + protected String[] natureSets; + protected String[] builderIds; + protected String[] contentTypeIds; + protected boolean allowLinking = true; + + //descriptors that are in a dependency cycle are never valid + protected boolean hasCycle = false; + //colours used by cycle detection algorithm + protected byte colour = 0; + + /** + * Creates a new descriptor based on the given extension markup. + * @exception CoreException if the given nature extension is not correctly formed. + */ + protected ProjectNatureDescriptor(IExtension natureExtension) throws CoreException { + readExtension(natureExtension); + } + + protected void fail() throws CoreException { + fail(NLS.bind(Messages.natures_invalidDefinition, id)); + } + + protected void fail(String reason) throws CoreException { + throw new ResourceException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, reason, null)); + } + + /** + * Returns the IDs of the incremental builders that this nature claims to + * own. These builders do not necessarily exist in the registry. + */ + public String[] getBuilderIds() { + return builderIds; + } + + /** + * Returns the IDs of the content types this nature declares to + * have affinity with. These content types do not necessarily exist in the registry. + */ + public String[] getContentTypeIds() { + return contentTypeIds; + } + + /** + * @see IProjectNatureDescriptor#getNatureId() + */ + @Override + public String getNatureId() { + return id; + } + + /** + * @see IProjectNatureDescriptor#getLabel() + */ + @Override + public String getLabel() { + return label; + } + + /** + * @see IProjectNatureDescriptor#getRequiredNatureIds() + */ + @Override + public String[] getRequiredNatureIds() { + return requiredNatures; + } + + /** + * @see IProjectNatureDescriptor#getNatureSetIds() + */ + @Override + public String[] getNatureSetIds() { + return natureSets; + } + + /** + * @see IProjectNatureDescriptor#isLinkingAllowed() + */ + @Override + public boolean isLinkingAllowed() { + return allowLinking; + } + + /** + * Initialize this nature descriptor based on the provided extension point. + */ + protected void readExtension(IExtension natureExtension) throws CoreException { + //read the extension + id = natureExtension.getUniqueIdentifier(); + if (id == null) { + fail(Messages.natures_missingIdentifier); + } + label = natureExtension.getLabel(); + IConfigurationElement[] elements = natureExtension.getConfigurationElements(); + int count = elements.length; + ArrayList requiredList = new ArrayList<>(count); + ArrayList setList = new ArrayList<>(count); + ArrayList builderList = new ArrayList<>(count); + ArrayList contentTypeList = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + IConfigurationElement element = elements[i]; + String name = element.getName(); + if (name.equalsIgnoreCase("requires-nature")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + requiredList.add(attribute); + } else if (name.equalsIgnoreCase("one-of-nature")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + setList.add(attribute); + } else if (name.equalsIgnoreCase("builder")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + builderList.add(attribute); + } else if (name.equalsIgnoreCase("content-type")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(); + contentTypeList.add(attribute); + } else if (name.equalsIgnoreCase("options")) { //$NON-NLS-1$ + String attribute = element.getAttribute("allowLinking"); //$NON-NLS-1$ + //when in doubt (missing attribute, wrong value) default to allow linking + allowLinking = !Boolean.FALSE.toString().equalsIgnoreCase(attribute); + } + } + requiredNatures = requiredList.toArray(new String[requiredList.size()]); + natureSets = setList.toArray(new String[setList.size()]); + builderIds = builderList.toArray(new String[builderList.size()]); + contentTypeIds = contentTypeList.toArray(new String[contentTypeList.size()]); + } + + /** + * Prints out a string representation for debugging purposes only. + */ + @Override + public String toString() { + return "ProjectNatureDescriptor(" + id + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java new file mode 100644 index 0000000000..448478b3dd --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPathVariableManager.java @@ -0,0 +1,502 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import org.eclipse.core.filesystem.URIUtil; +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; + +/** + * The {@link IPathVariableManager} for a single project + * @see IProject#getPathVariableManager() + */ +public class ProjectPathVariableManager implements IPathVariableManager, IManager { + + private Resource resource; + private ProjectVariableProviderManager.Descriptor variableProviders[] = null; + + /** + * Constructor for the class. + */ + public ProjectPathVariableManager(Resource resource) { + this.resource = resource; + variableProviders = ProjectVariableProviderManager.getDefault().getDescriptors(); + } + + PathVariableManager getWorkspaceManager() { + return (PathVariableManager) resource.getWorkspace().getPathVariableManager(); + } + + /** + * Throws a runtime exception if the given name is not valid as a path + * variable name. + */ + private void checkIsValidName(String name) throws CoreException { + IStatus status = validateName(name); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * Throws an exception if the given path is not valid as a path variable + * value. + */ + private void checkIsValidValue(URI newValue) throws CoreException { + IStatus status = validateValue(newValue); + if (!status.isOK()) + throw new CoreException(status); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#getPathVariableNames() + */ + @Override + public String[] getPathVariableNames() { + List result = new LinkedList<>(); + HashMap map; + try { + map = ((ProjectDescription) resource.getProject().getDescription()).getVariables(); + } catch (CoreException e) { + return new String[0]; + } + for (int i = 0; i < variableProviders.length; i++) { + String[] variableHints = variableProviders[i].getVariableNames(variableProviders[i].getName(), resource); + if (variableHints != null && variableHints.length > 0) + for (int k = 0; k < variableHints.length; k++) + result.add(variableProviders[i].getVariableNames(variableProviders[i].getName(), resource)[k]); + } + if (map != null) + result.addAll(map.keySet()); + result.addAll(Arrays.asList(getWorkspaceManager().getPathVariableNames())); + return result.toArray(new String[0]); + } + + /** + * @deprecated use {@link #getURIValue(String)} instead. + */ + @Deprecated + @Override + public IPath getValue(String varName) { + URI uri = getURIValue(varName); + if (uri != null) + return URIUtil.toPath(uri); + return null; + } + + /** + * If the variable is not listed in the project description, we fall back on + * the workspace variables. + * + * @see org.eclipse.core.resources.IPathVariableManager#getURIValue(String) + */ + @Override + public URI getURIValue(String varName) { + String value = internalGetValue(varName); + if (value != null) { + if (value.indexOf("..") != -1) { //$NON-NLS-1$ + // if the path is 'reducible', lets resolve it first. + int index = value.indexOf(IPath.SEPARATOR); + if (index > 0) { // if its the first character, its an + // absolute path on unix, so we don't + // resolve it + URI resolved = resolveVariable(value); + if (resolved != null) + return resolved; + } + } + try { + return URI.create(value); + } catch (IllegalArgumentException e) { + IPath path = Path.fromPortableString(value); + return URIUtil.toURI(path); + } + } + return getWorkspaceManager().getURIValue(varName); + } + + public String internalGetValue(String varName) { + HashMap map; + try { + map = ((ProjectDescription) resource.getProject().getDescription()).getVariables(); + } catch (CoreException e) { + return null; + } + if (map != null && map.containsKey(varName)) + return map.get(varName).getValue(); + + String name; + int index = varName.indexOf('-'); + if (index != -1) + name = varName.substring(0, index); + else + name = varName; + for (int i = 0; i < variableProviders.length; i++) { + if (variableProviders[i].getName().equals(name)) + return variableProviders[i].getValue(varName, resource); + } + for (int i = 0; i < variableProviders.length; i++) { + if (name.startsWith(variableProviders[i].getName())) + return variableProviders[i].getValue(varName, resource); + } + return null; + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#isDefined(String) + */ + @Override + public boolean isDefined(String varName) { + for (int i = 0; i < variableProviders.length; i++) { + // if (variableProviders[i].getName().equals(varName)) + // return true; + + if (varName.startsWith(variableProviders[i].getName())) + return true; + } + + try { + HashMap map = ((ProjectDescription) resource.getProject().getDescription()).getVariables(); + if (map != null) { + Iterator it = map.keySet().iterator(); + while (it.hasNext()) { + String name = it.next(); + if (name.equals(varName)) + return true; + } + } + } catch (CoreException e) { + return false; + } + boolean value = getWorkspaceManager().isDefined(varName); + if (!value) { + // this is to handle variables with encoded arguments + int index = varName.indexOf('-'); + if (index != -1) { + String newVarName = varName.substring(0, index); + value = isDefined(newVarName); + } + } + return value; + } + + /** + * @deprecated use {@link #resolveURI(URI)} instead. + */ + @Override + @Deprecated + public IPath resolvePath(IPath path) { + if (path == null || path.segmentCount() == 0 || path.isAbsolute() || path.getDevice() != null) + return path; + URI value = resolveURI(URIUtil.toURI(path)); + return value == null ? path : URIUtil.toPath(value); + } + + public URI resolveVariable(String variable) { + LinkedList variableStack = new LinkedList<>(); + + String value = resolveVariable(variable, variableStack); + if (value != null) { + try { + return URI.create(value); + } catch (IllegalArgumentException e) { + return URIUtil.toURI(Path.fromPortableString(value)); + } + } + return null; + } + + public String resolveVariable(String value, LinkedList variableStack) { + if (variableStack == null) + variableStack = new LinkedList<>(); + + String tmp = internalGetValue(value); + if (tmp == null) { + URI result = getWorkspaceManager().getURIValue(value); + if (result != null) + return result.toASCIIString(); + } else + value = tmp; + + while (true) { + String stringValue; + try { + URI uri = URI.create(value); + if (uri != null) { + IPath path = URIUtil.toPath(uri); + if (path != null) + stringValue = path.toPortableString(); + else + stringValue = value; + } else + stringValue = value; + } catch (IllegalArgumentException e) { + stringValue = value; + } + // we check if the value contains referenced variables with ${VAR} + int index = stringValue.indexOf("${"); //$NON-NLS-1$ + if (index != -1) { + int endIndex = PathVariableUtil.getMatchingBrace(stringValue, index); + String macro = stringValue.substring(index + 2, endIndex); + String resolvedMacro = ""; //$NON-NLS-1$ + if (!variableStack.contains(macro)) { + variableStack.add(macro); + resolvedMacro = resolveVariable(macro, variableStack); + if (resolvedMacro == null) + resolvedMacro = ""; //$NON-NLS-1$ + } + if (stringValue.length() > endIndex) + stringValue = stringValue.substring(0, index) + resolvedMacro + stringValue.substring(endIndex + 1); + else + stringValue = resolvedMacro; + value = stringValue; + } else + break; + } + return value; + } + + @Override + public URI resolveURI(URI uri) { + if (uri == null || uri.isAbsolute() || (uri.getSchemeSpecificPart() == null)) + return uri; + IPath raw = new Path(uri.getSchemeSpecificPart()); + if (raw == null || raw.segmentCount() == 0 || raw.isAbsolute() || raw.getDevice() != null) + return URIUtil.toURI(raw); + URI value = resolveVariable(raw.segment(0)); + if (value == null) + return uri; + + String path = value.getPath(); + if (path != null) { + IPath p = Path.fromPortableString(path); + p = p.append(raw.removeFirstSegments(1)); + try { + value = new URI(value.getScheme(), value.getHost(), p.toPortableString(), value.getFragment()); + } catch (URISyntaxException e) { + return uri; + } + return value; + } + return uri; + } + + /** + * @deprecated use {@link #setURIValue(String, URI)} instead. + */ + @Deprecated + @Override + public void setValue(String varName, IPath newValue) throws CoreException { + if (newValue == null) + setURIValue(varName, (URI) null); + else + setURIValue(varName, URIUtil.toURI(newValue)); + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#setValue(String, + * IPath) + */ + @Override + public void setURIValue(String varName, URI newValue) throws CoreException { + checkIsValidName(varName); + checkIsValidValue(newValue); + // read previous value and set new value atomically in order to generate + // the right event + boolean changeWorkspaceValue = false; + Project project = (Project) resource.getProject(); + int eventType = 0; + synchronized (this) { + String value = internalGetValue(varName); + URI currentValue = null; + if (value == null) + currentValue = getWorkspaceManager().getURIValue(varName); + else { + try { + currentValue = URI.create(value); + } catch (IllegalArgumentException e) { + currentValue = null; + } + } + boolean variableExists = currentValue != null; + if (!variableExists && newValue == null) + return; + if (variableExists && currentValue.equals(newValue)) + return; + + for (int i = 0; i < variableProviders.length; i++) { + // if (variableProviders[i].getName().equals(varName)) + // return; + + if (varName.startsWith(variableProviders[i].getName())) + return; + } + + if (value == null && variableExists) + changeWorkspaceValue = true; + else { + IProgressMonitor monitor = new NullProgressMonitor(); + final ISchedulingRule rule = resource.getProject(); // project.workspace.getRuleFactory().modifyRule(project); + try { + project.workspace.prepareOperation(rule, monitor); + project.workspace.beginOperation(true); + // save the location in the project description + ProjectDescription description = project.internalGetDescription(); + if (newValue == null) { + description.setVariableDescription(varName, null); + eventType = IPathVariableChangeEvent.VARIABLE_DELETED; + } else { + description.setVariableDescription(varName, new VariableDescription(varName, newValue.toASCIIString())); + eventType = variableExists ? IPathVariableChangeEvent.VARIABLE_CHANGED : IPathVariableChangeEvent.VARIABLE_CREATED; + } + project.writeDescription(IResource.NONE); + } finally { + project.workspace.endOperation(rule, true); + } + } + } + if (changeWorkspaceValue) + getWorkspaceManager().setURIValue(varName, newValue); + else { + // notify listeners from outside the synchronized block to avoid deadlocks + getWorkspaceManager().fireVariableChangeEvent(project, varName, newValue != null ? URIUtil.toPath(newValue) : null, eventType); + } + } + + /** + * @see org.eclipse.core.internal.resources.IManager#shutdown(IProgressMonitor) + */ + @Override + public void shutdown(IProgressMonitor monitor) { + // nothing to do here + } + + /** + * @see org.eclipse.core.internal.resources.IManager#startup(IProgressMonitor) + */ + @Override + public void startup(IProgressMonitor monitor) { + // nothing to do here + } + + /** + * @see org.eclipse.core.resources.IPathVariableManager#validateName(String) + */ + @Override + public IStatus validateName(String name) { + String message = null; + if (name.length() == 0) { + message = Messages.pathvar_length; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + char first = name.charAt(0); + if (!Character.isLetter(first) && first != '_') { + message = NLS.bind(Messages.pathvar_beginLetter, String.valueOf(first)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + + for (int i = 1; i < name.length(); i++) { + char following = name.charAt(i); + if (Character.isWhitespace(following)) + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, Messages.pathvar_whitespace); + if (!Character.isLetter(following) && !Character.isDigit(following) && following != '_') { + message = NLS.bind(Messages.pathvar_invalidChar, String.valueOf(following)); + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + } + // check + + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#validateValue(IPath) + */ + @Override + public IStatus validateValue(IPath value) { + // accept any format + return Status.OK_STATUS; + } + + /** + * @see IPathVariableManager#validateValue(URI) + */ + @Override + public IStatus validateValue(URI value) { + // accept any format + return Status.OK_STATUS; + } + + /** + * @throws CoreException + * @see IPathVariableManager#convertToRelative(URI, boolean, String) + */ + @Override + public URI convertToRelative(URI path, boolean force, String variableHint) throws CoreException { + return PathVariableUtil.convertToRelative(this, path, resource, force, variableHint); + } + + /** + * @see IPathVariableManager#convertToUserEditableFormat(String, boolean) + */ + @Override + public String convertToUserEditableFormat(String value, boolean locationFormat) { + return PathVariableUtil.convertToUserEditableFormatInternal(value, locationFormat); + } + + @Override + public String convertFromUserEditableFormat(String userFormat, boolean locationFormat) { + return PathVariableUtil.convertFromUserEditableFormatInternal(this, userFormat, locationFormat); + } + + @Override + public void addChangeListener(IPathVariableChangeListener listener) { + getWorkspaceManager().addChangeListener(listener, resource.getProject()); + } + + @Override + public void removeChangeListener(IPathVariableChangeListener listener) { + getWorkspaceManager().removeChangeListener(listener, resource.getProject()); + } + + @Override + public URI getVariableRelativePathLocation(URI location) { + try { + URI result = convertToRelative(location, false, null); + if (!result.equals(location)) + return result; + } catch (CoreException e) { + // handled by returning null + } + return null; + } + + /* + * Return the resource of this manager. + */ + public IResource getResource() { + return resource; + } + + @Override + public boolean isUserDefined(String name) { + return ProjectVariableProviderManager.getDefault().findDescriptor(name) == null; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java new file mode 100644 index 0000000000..8ffb91aeb1 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectPreferences.java @@ -0,0 +1,689 @@ +/******************************************************************************* + * 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 + * Markus Schorn (Wind River) - [108066] Project prefs marked dirty on read + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427, 483529 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.*; +import org.eclipse.core.internal.preferences.*; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IExportedPreferences; +import org.eclipse.osgi.util.NLS; +import org.osgi.service.prefs.BackingStoreException; +import org.osgi.service.prefs.Preferences; + +/** + * Represents a node in the Eclipse preference hierarchy which stores preference + * values for projects. + * + * @since 3.0 + */ +public class ProjectPreferences extends EclipsePreferences { + static final String PREFS_REGULAR_QUALIFIER = ResourcesPlugin.PI_RESOURCES; + static final String PREFS_DERIVED_QUALIFIER = PREFS_REGULAR_QUALIFIER + ".derived"; //$NON-NLS-1$ + /** + * Cache which nodes have been loaded from disk + */ + protected static Set loadedNodes = Collections.synchronizedSet(new HashSet()); + private IFile file; + private boolean initialized = false; + /** + * Flag indicating that this node is currently reading values from disk, + * to avoid flushing during a read. + */ + private boolean isReading; + /** + * Flag indicating that this node is currently writing values to disk, + * to avoid re-reading after the write completes. + */ + private boolean isWriting; + private IEclipsePreferences loadLevel; + private IProject project; + private String qualifier; + + // cache + private int segmentCount; + + static void deleted(IFile file) throws CoreException { + IPath path = file.getFullPath(); + int count = path.segmentCount(); + if (count != 3) + return; + // check if we are in the .settings directory + if (!EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME.equals(path.segment(1))) + return; + Preferences root = Platform.getPreferencesService().getRootNode(); + String project = path.segment(0); + String qualifier = path.removeFileExtension().lastSegment(); + ProjectPreferences projectNode = (ProjectPreferences) root.node(ProjectScope.SCOPE).node(project); + // if the node isn't known then just return + try { + if (!projectNode.nodeExists(qualifier)) + return; + } catch (BackingStoreException e) { + // ignore + } + + // clear the preferences + clearNode(projectNode.node(qualifier)); + + // notifies the CharsetManager if needed + if (qualifier.equals(PREFS_REGULAR_QUALIFIER) || qualifier.equals(PREFS_DERIVED_QUALIFIER)) + preferencesChanged(file.getProject()); + } + + static void deleted(IFolder folder) throws CoreException { + IPath path = folder.getFullPath(); + int count = path.segmentCount(); + if (count != 2) + return; + // check if we are the .settings directory + if (!EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME.equals(path.segment(1))) + return; + Preferences root = Platform.getPreferencesService().getRootNode(); + // The settings dir has been removed/moved so remove all project prefs + // for the resource. + String project = path.segment(0); + Preferences projectNode = root.node(ProjectScope.SCOPE).node(project); + // check if we need to notify the charset manager + boolean hasResourcesSettings = getFile(folder, PREFS_REGULAR_QUALIFIER).exists() || getFile(folder, PREFS_DERIVED_QUALIFIER).exists(); + // remove the preferences + removeNode(projectNode); + // notifies the CharsetManager + if (hasResourcesSettings) + preferencesChanged(folder.getProject()); + } + + /* + * The whole project has been removed so delete all of the project settings + */ + static void deleted(IProject project) throws CoreException { + // The settings dir has been removed/moved so remove all project prefs + // for the resource. We have to do this now because (since we aren't + // synchronizing) there is short-circuit code that doesn't visit the + // children. + Preferences root = Platform.getPreferencesService().getRootNode(); + Preferences projectNode = root.node(ProjectScope.SCOPE).node(project.getName()); + // check if we need to notify the charset manager + boolean hasResourcesSettings = getFile(project, PREFS_REGULAR_QUALIFIER).exists() || getFile(project, PREFS_DERIVED_QUALIFIER).exists(); + // remove the preferences + removeNode(projectNode); + // notifies the CharsetManager + if (hasResourcesSettings) + preferencesChanged(project); + } + + static void deleted(IResource resource) throws CoreException { + switch (resource.getType()) { + case IResource.FILE : + deleted((IFile) resource); + return; + case IResource.FOLDER : + deleted((IFolder) resource); + return; + case IResource.PROJECT : + deleted((IProject) resource); + return; + } + } + + /* + * Return the preferences file for the given folder and qualifier. + */ + static IFile getFile(IFolder folder, String qualifier) { + Assert.isLegal(folder.getName().equals(DEFAULT_PREFERENCES_DIRNAME)); + return folder.getFile(new Path(qualifier).addFileExtension(PREFS_FILE_EXTENSION)); + } + + /* + * Return the preferences file for the given project and qualifier. + */ + static IFile getFile(IProject project, String qualifier) { + return project.getFile(new Path(DEFAULT_PREFERENCES_DIRNAME).append(qualifier).addFileExtension(PREFS_FILE_EXTENSION)); + } + + private static Properties loadProperties(IFile file) throws BackingStoreException { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Loading preferences from file: " + file.getFullPath()); //$NON-NLS-1$ + Properties result = new Properties(); + InputStream input = null; + try { + input = new BufferedInputStream(file.getContents(true)); + result.load(input); + } catch (CoreException e) { + String message = NLS.bind(Messages.preferences_loadException, file.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } catch (IOException e) { + String message = NLS.bind(Messages.preferences_loadException, file.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } finally { + FileUtil.safeClose(input); + } + return result; + } + + private static void preferencesChanged(IProject project) { + Workspace workspace = ((Workspace) ResourcesPlugin.getWorkspace()); + workspace.getCharsetManager().projectPreferencesChanged(project); + workspace.getContentDescriptionManager().projectPreferencesChanged(project); + } + + private static void read(ProjectPreferences node, IFile file) throws BackingStoreException, CoreException { + if (file == null || !file.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Unable to determine preference file or file does not exist for node: " + node.absolutePath()); //$NON-NLS-1$ + return; + } + Properties fromDisk = loadProperties(file); + // no work to do + if (fromDisk.isEmpty()) + return; + // create a new node to store the preferences in. + IExportedPreferences myNode = (IExportedPreferences) ExportedPreferences.newRoot().node(node.absolutePath()); + convertFromProperties((EclipsePreferences) myNode, fromDisk, false); + //flag that we are currently reading, to avoid unnecessary writing + boolean oldIsReading = node.isReading; + node.isReading = true; + try { + Platform.getPreferencesService().applyPreferences(myNode); + } finally { + node.isReading = oldIsReading; + } + } + + static void removeNode(Preferences node) throws CoreException { + String message = NLS.bind(Messages.preferences_removeNodeException, node.absolutePath()); + try { + node.removeNode(); + } catch (BackingStoreException e) { + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e); + throw new CoreException(status); + } + removeLoadedNodes(node); + } + + static void clearNode(Preferences node) throws CoreException { + // if the underlying properties file was deleted, clear the values and remove + // it from the list of loaded nodes, keep the node as it might still be referenced + try { + clearAll(node); + } catch (BackingStoreException e) { + String message = NLS.bind(Messages.preferences_clearNodeException, node.absolutePath()); + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e); + throw new CoreException(status); + } + removeLoadedNodes(node); + } + + private static void clearAll(Preferences node) throws BackingStoreException { + node.clear(); + String[] names = node.childrenNames(); + for (int i = 0; i < names.length; i++) { + clearAll(node.node(names[i])); + } + } + + private static void removeLoadedNodes(Preferences node) { + String path = node.absolutePath(); + synchronized (loadedNodes) { + for (Iterator i = loadedNodes.iterator(); i.hasNext();) { + String key = i.next(); + if (key.startsWith(path)) + i.remove(); + } + } + } + + public static void updatePreferences(IFile file) throws CoreException { + IPath path = file.getFullPath(); + // if we made it this far we are inside /project/.settings and might + // have a change to a preference file + if (!PREFS_FILE_EXTENSION.equals(path.getFileExtension())) + return; + + String project = path.segment(0); + String qualifier = path.removeFileExtension().lastSegment(); + Preferences root = Platform.getPreferencesService().getRootNode(); + Preferences node = root.node(ProjectScope.SCOPE).node(project).node(qualifier); + String message = null; + try { + message = NLS.bind(Messages.preferences_syncException, node.absolutePath()); + if (!(node instanceof ProjectPreferences)) + return; + ProjectPreferences projectPrefs = (ProjectPreferences) node; + if (projectPrefs.isWriting) + return; + read(projectPrefs, file); + // Bug 108066: In case the node had existed before it was updated from + // file, the read() operation marks it dirty. Override the dirty flag + // since we know that the node is expected to be in sync with the file. + projectPrefs.dirty = false; + + // make sure that we generate the appropriate resource change events + // if encoding settings have changed + if (PREFS_REGULAR_QUALIFIER.equals(qualifier) || PREFS_DERIVED_QUALIFIER.equals(qualifier)) + preferencesChanged(file.getProject()); + } catch (BackingStoreException e) { + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e); + throw new CoreException(status); + } + } + + /** + * Default constructor. Should only be called by #createExecutableExtension. + */ + public ProjectPreferences() { + super(null, null); + } + + private ProjectPreferences(EclipsePreferences parent, String name) { + super(parent, name); + + // cache the segment count + String path = absolutePath(); + segmentCount = getSegmentCount(path); + + if (segmentCount == 1) + return; + + // cache the project name + String projectName = getSegment(path, 1); + if (projectName != null) + project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); + + // cache the qualifier + if (segmentCount > 2) + qualifier = getSegment(path, 2); + } + + @Override + public String[] childrenNames() throws BackingStoreException { + // illegal state if this node has been removed + checkRemoved(); + initialize(); + silentLoad(); + return super.childrenNames(); + } + + @Override + public void clear() { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + super.clear(); + } + + /* + * Figure out what the children of this node are based on the resources + * that are in the workspace. + */ + private String[] computeChildren() { + if (project == null) + return EMPTY_STRING_ARRAY; + IFolder folder = project.getFolder(DEFAULT_PREFERENCES_DIRNAME); + if (!folder.exists()) + return EMPTY_STRING_ARRAY; + IResource[] members = null; + try { + members = folder.members(); + } catch (CoreException e) { + return EMPTY_STRING_ARRAY; + } + ArrayList result = new ArrayList<>(); + for (int i = 0; i < members.length; i++) { + IResource resource = members[i]; + if (resource.getType() == IResource.FILE && PREFS_FILE_EXTENSION.equals(resource.getFullPath().getFileExtension())) + result.add(resource.getFullPath().removeFileExtension().lastSegment()); + } + return result.toArray(EMPTY_STRING_ARRAY); + } + + @Override + public void flush() throws BackingStoreException { + if (isReading) + return; + isWriting = true; + try { + // call the internal method because we don't want to be synchronized, we will do that ourselves later. + IEclipsePreferences toFlush = super.internalFlush(); + //if we aren't at the right level, then flush the appropriate node + if (toFlush != null) + toFlush.flush(); + } finally { + isWriting = false; + } + } + + private IFile getFile() { + if (file == null) { + if (project == null || qualifier == null) + return null; + file = getFile(project, qualifier); + } + return file; + } + + /* + * Return the node at which these preferences are loaded/saved. + */ + @Override + protected IEclipsePreferences getLoadLevel() { + if (loadLevel == null) { + if (project == null || qualifier == null) + return null; + // Make it relative to this node rather than navigating to it from the root. + // Walk backwards up the tree starting at this node. + // This is important to avoid a chicken/egg thing on startup. + EclipsePreferences node = this; + for (int i = 3; i < segmentCount; i++) + node = (EclipsePreferences) node.parent(); + loadLevel = node; + } + return loadLevel; + } + + /* + * Calculate and return the file system location for this preference node. + * Use the absolute path of the node to find out the project name so + * we can get its location on disk. + * + * NOTE: we cannot cache the location since it may change over the course + * of the project life-cycle. + */ + @Override + protected IPath getLocation() { + if (project == null || qualifier == null) + return null; + IPath path = project.getLocation(); + return computeLocation(path, qualifier); + } + + @Override + protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) { + return new ProjectPreferences(nodeParent, nodeName); + } + + @Override + protected String internalGet(String key) { + // throw NPE if key is null + if (key == null) + throw new NullPointerException(); + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + return super.internalGet(key); + } + + @Override + protected String internalPut(String key, String newValue) { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + if ((segmentCount == 3) && PREFS_REGULAR_QUALIFIER.equals(qualifier) && (project != null)) { + if (ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS.equals(key)) { + CharsetManager charsetManager = ((Workspace) ResourcesPlugin.getWorkspace()).getCharsetManager(); + if (Boolean.parseBoolean(newValue)) + charsetManager.splitEncodingPreferences(project); + else + charsetManager.mergeEncodingPreferences(project); + } + } + return super.internalPut(key, newValue); + } + + private void initialize() { + if (segmentCount != 2) + return; + + // if already initialized, then skip this initialization + if (initialized) + return; + + // initialize the children only if project is opened + if (project.isOpen()) { + try { + synchronized (this) { + List addedNames = Arrays.asList(internalChildNames()); + String[] names = computeChildren(); + // add names only for nodes that were not added previously + for (int i = 0; i < names.length; i++) + if (!addedNames.contains(names[i])) + addChild(names[i], null); + } + } finally { + // mark as initialized so that subsequent project opening will not initialize preferences again + initialized = true; + } + } + } + + @Override + protected boolean isAlreadyLoaded(IEclipsePreferences node) { + return loadedNodes.contains(node.absolutePath()); + } + + @Override + public String[] keys() { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + return super.keys(); + } + + @Override + protected void load() throws BackingStoreException { + load(true); + } + + private void load(boolean reportProblems) throws BackingStoreException { + IFile localFile = getFile(); + if (localFile == null || !localFile.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Unable to determine preference file or file does not exist for node: " + absolutePath()); //$NON-NLS-1$ + return; + } + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Loading preferences from file: " + localFile.getFullPath()); //$NON-NLS-1$ + Properties fromDisk = new Properties(); + InputStream input = null; + try { + input = new BufferedInputStream(localFile.getContents(true)); + fromDisk.load(input); + convertFromProperties(this, fromDisk, true); + loadedNodes.add(absolutePath()); + } catch (CoreException e) { + if (reportProblems) { + String message = NLS.bind(Messages.preferences_loadException, localFile.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } + } catch (IOException e) { + if (reportProblems) { + String message = NLS.bind(Messages.preferences_loadException, localFile.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } + } finally { + FileUtil.safeClose(input); + } + } + + /** + * If we are at the /project node and we are checking for the existence of a child, we + * want special behaviour. If the child is a single segment name, then we want to + * return true if the node exists OR if a project with that name exists in the workspace. + */ + @Override + public boolean nodeExists(String path) throws BackingStoreException { + // short circuit for checking this node + if (path.length() == 0) + return !removed; + // illegal state if this node has been removed. + // do this AFTER checking for the empty string. + checkRemoved(); + initialize(); + silentLoad(); + if (segmentCount != 1) + return super.nodeExists(path); + if (path.length() == 0) + return super.nodeExists(path); + if (path.charAt(0) == IPath.SEPARATOR) + return super.nodeExists(path); + if (path.indexOf(IPath.SEPARATOR) != -1) + return super.nodeExists(path); + // if we are checking existance of a single segment child of /project, base the answer on + // whether or not it exists in the workspace. + return ResourcesPlugin.getWorkspace().getRoot().getProject(path).exists() || super.nodeExists(path); + } + + @Override + public void remove(String key) { + // illegal state if this node has been removed + checkRemoved(); + silentLoad(); + super.remove(key); + if ((segmentCount == 3) && PREFS_REGULAR_QUALIFIER.equals(qualifier) && (project != null)) { + if (ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS.equals(key)) { + CharsetManager charsetManager = ((Workspace) ResourcesPlugin.getWorkspace()).getCharsetManager(); + if (ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS) + charsetManager.splitEncodingPreferences(project); + else + charsetManager.mergeEncodingPreferences(project); + } + } + } + + @Override + protected void save() throws BackingStoreException { + final IFile fileInWorkspace = getFile(); + if (fileInWorkspace == null) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Not saving preferences since there is no file for node: " + absolutePath()); //$NON-NLS-1$ + return; + } + final String finalQualifier = qualifier; + final BackingStoreException[] bse = new BackingStoreException[1]; + try { + ICoreRunnable operation = new ICoreRunnable() { + @Override + public void run(IProgressMonitor monitor) throws CoreException { + try { + Properties table = convertToProperties(new SortedProperties(), ""); //$NON-NLS-1$ + // nothing to save. delete existing file if one exists. + if (table.isEmpty()) { + if (fileInWorkspace.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Deleting preference file: " + fileInWorkspace.getFullPath()); //$NON-NLS-1$ + if (fileInWorkspace.isReadOnly()) { + IStatus status = fileInWorkspace.getWorkspace().validateEdit(new IFile[] {fileInWorkspace}, IWorkspace.VALIDATE_PROMPT); + if (!status.isOK()) + throw new CoreException(status); + } + try { + fileInWorkspace.delete(true, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.preferences_deleteException, fileInWorkspace.getFullPath()); + log(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IStatus.WARNING, message, null)); + } + } + return; + } + table.put(VERSION_KEY, VERSION_VALUE); + // print the table to a string and remove the timestamp that Properties#store always adds + String s = removeTimestampFromTable(table); + String systemLineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$ + String fileLineSeparator = FileUtil.getLineSeparator(fileInWorkspace); + if (!systemLineSeparator.equals(fileLineSeparator)) + s = s.replaceAll(systemLineSeparator, fileLineSeparator); + InputStream input = new BufferedInputStream(new ByteArrayInputStream(s.getBytes("UTF-8"))); //$NON-NLS-1$ + if (fileInWorkspace.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Setting preference file contents for: " + fileInWorkspace.getFullPath()); //$NON-NLS-1$ + if (fileInWorkspace.isReadOnly()) { + IStatus status = fileInWorkspace.getWorkspace().validateEdit(new IFile[] {fileInWorkspace}, IWorkspace.VALIDATE_PROMPT); + if (!status.isOK()) { + input.close(); + throw new CoreException(status); + } + } + // set the contents + fileInWorkspace.setContents(input, IResource.KEEP_HISTORY, null); + } else { + // create the file + IFolder folder = (IFolder) fileInWorkspace.getParent(); + if (!folder.exists()) { + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Creating parent preference directory: " + folder.getFullPath()); //$NON-NLS-1$ + folder.create(IResource.NONE, true, null); + } + if (Policy.DEBUG_PREFERENCES) + Policy.debug("Creating preference file: " + fileInWorkspace.getLocation()); //$NON-NLS-1$ + fileInWorkspace.create(input, IResource.NONE, null); + } + if (PREFS_DERIVED_QUALIFIER.equals(finalQualifier)) + fileInWorkspace.setDerived(true, null); + } catch (BackingStoreException e) { + bse[0] = e; + } catch (IOException e) { + String message = NLS.bind(Messages.preferences_saveProblems, fileInWorkspace.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + bse[0] = new BackingStoreException(message); + } + } + }; + //don't bother with scheduling rules if we are already inside an operation + try { + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + if (((Workspace) workspace).getWorkManager().isLockAlreadyAcquired()) { + operation.run(null); + } else { + IResourceRuleFactory factory = workspace.getRuleFactory(); + // we might: delete the file, create the .settings folder, create the file, modify the file, or set derived flag for the file. + ISchedulingRule rule = MultiRule.combine(new ISchedulingRule[] {factory.deleteRule(fileInWorkspace), factory.createRule(fileInWorkspace.getParent()), factory.modifyRule(fileInWorkspace), factory.derivedRule(fileInWorkspace)}); + workspace.run(operation, rule, IResource.NONE, null); + if (bse[0] != null) + throw bse[0]; + } + } catch (OperationCanceledException e) { + throw new BackingStoreException(Messages.preferences_operationCanceled); + } + } catch (CoreException e) { + String message = NLS.bind(Messages.preferences_saveProblems, fileInWorkspace.getFullPath()); + log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e)); + throw new BackingStoreException(message); + } + } + + private void silentLoad() { + ProjectPreferences node = (ProjectPreferences) getLoadLevel(); + if (node == null) + return; + if (isAlreadyLoaded(node) || node.isLoading()) + return; + try { + node.setLoading(true); + node.load(false); + } catch (BackingStoreException e) { + // will not happen, all exceptions are swallowed by load(false) + } finally { + node.setLoading(false); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java new file mode 100644 index 0000000000..c4951f6ce6 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ProjectVariableProviderManager.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.HashMap; +import java.util.Map; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * Repository for all variable providers available through the extension points. + * @since 3.6 + */ +public class ProjectVariableProviderManager { + + public static class Descriptor { + PathVariableResolver provider = null; + String name = null; + String value = null; + + public Descriptor(IExtension extension, IConfigurationElement element) throws RuntimeException, CoreException { + name = element.getAttribute("variable"); //$NON-NLS-1$ + value = element.getAttribute("value"); //$NON-NLS-1$ + try { + String classAttribute = "class"; //$NON-NLS-1$ + if (element.getAttribute(classAttribute) != null) + provider = (PathVariableResolver) element.createExecutableExtension(classAttribute); + } catch (CoreException e) { + Policy.log(e); + } + if (name == null) + fail(NLS.bind(Messages.mapping_invalidDef, extension.getUniqueIdentifier())); + } + + protected void fail(String reason) throws CoreException { + throw new ResourceException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, reason, null)); + } + + public String getName() { + return name; + } + + public String getValue(String variable, IResource resource) { + if (value != null) + return value; + return provider.getValue(variable, resource); + } + + public String[] getVariableNames(String variable, IResource resource) { + if (provider != null) + return provider.getVariableNames(variable, resource); + if (name.equals(variable)) + return new String[] {variable}; + return null; + } + } + + private static Map descriptors; + private static Descriptor[] descriptorsArray; + private static ProjectVariableProviderManager instance = new ProjectVariableProviderManager(); + + public static ProjectVariableProviderManager getDefault() { + return instance; + } + + public Descriptor[] getDescriptors() { + lazyInitialize(); + return descriptorsArray; + } + + protected void lazyInitialize() { + if (descriptors != null) + return; + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_VARIABLE_PROVIDERS); + IExtension[] extensions = point.getExtensions(); + descriptors = new HashMap<>(extensions.length * 2 + 1); + for (int i = 0, imax = extensions.length; i < imax; i++) { + IConfigurationElement[] elements = extensions[i].getConfigurationElements(); + int count = elements.length; + for (int j = 0; j < count; j++) { + IConfigurationElement element = elements[j]; + String elementName = element.getName(); + if (elementName.equalsIgnoreCase("variableResolver")) { //$NON-NLS-1$ + Descriptor desc = null; + try { + desc = new Descriptor(extensions[i], element); + } catch (CoreException e) { + Policy.log(e); + } + if (desc != null) + descriptors.put(desc.getName(), desc); + } + } + } + descriptorsArray = descriptors.values().toArray(new Descriptor[descriptors.size()]); + } + + public Descriptor findDescriptor(String name) { + lazyInitialize(); + Descriptor result = descriptors.get(name); + return result; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java new file mode 100644 index 0000000000..bd1b8d2639 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RegexFileInfoMatcher.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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 development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.regex.*; +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.filtermatchers.AbstractFileInfoMatcher; +import org.eclipse.core.runtime.*; + +/** + * A Filter provider for Java Regular expression supported by + * java.util.regex.Pattern. + */ +public class RegexFileInfoMatcher extends AbstractFileInfoMatcher { + + Pattern pattern = null; + + public RegexFileInfoMatcher() { + // nothing to do + } + + @Override + public boolean matches(IContainer parent, IFileInfo fileInfo) { + if (pattern != null) { + Matcher m = pattern.matcher(fileInfo.getName()); + return m.matches(); + } + return false; + } + + @Override + public void initialize(IProject project, Object arguments) throws CoreException { + if (arguments != null) { + try { + pattern = Pattern.compile((String) arguments); + } catch (PatternSyntaxException e) { + throw new CoreException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, Platform.PLUGIN_ERROR, e.getMessage(), e)); + } + } + } +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java new file mode 100644 index 0000000000..e3bd02dac6 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Resource.java @@ -0,0 +1,2033 @@ +/******************************************************************************* + * 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 + * Dan Rubel - Implementation of getLocalTimeStamp + * Red Hat Incorporated - get/setResourceAttribute code + * Oakland Software Incorporated - added getSessionProperties and getPersistentProperties + * Holger Oehm - [226264] race condition in Workspace.isTreeLocked()/setTreeLocked() + * Martin Oberhuber (Wind River) - [245937] ProjectDescription#setLinkLocation() detects non-change + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support + * James Blackburn (Broadcom Corp.) - ongoing development + * Sergey Prigogin (Google) - [338010] Resource.createLink() does not preserve symbolic links + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.internal.events.LifecycleEvent; +import org.eclipse.core.internal.localstore.FileSystemResourceManager; +import org.eclipse.core.internal.properties.IPropertyManager; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.internal.watson.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IMoveDeleteHook; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; +import org.eclipse.osgi.util.NLS; + +public abstract class Resource extends PlatformObject implements IResource, ICoreConstants, Cloneable, IPathRequestor { + /* package */IPath path; + /* package */Workspace workspace; + + protected Resource(IPath path, Workspace workspace) { + this.path = path.removeTrailingSeparator(); + this.workspace = workspace; + } + + @Override + public void accept(IResourceProxyVisitor visitor, int memberFlags) throws CoreException { + accept(visitor, IResource.DEPTH_INFINITE, memberFlags); + } + + @Override + public void accept(final IResourceProxyVisitor visitor, final int depth, final int memberFlags) throws CoreException { + // It is invalid to call accept on a phantom when INCLUDE_PHANTOMS is not specified. + final boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0; + if ((memberFlags & IContainer.DO_NOT_CHECK_EXISTENCE) == 0) + checkAccessible(getFlags(getResourceInfo(includePhantoms, false))); + + final ResourceProxy proxy = new ResourceProxy(); + IElementContentVisitor elementVisitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object contents) { + ResourceInfo info = (ResourceInfo) contents; + if (!isMember(getFlags(info), memberFlags)) + return false; + proxy.requestor = requestor; + proxy.info = info; + try { + boolean shouldContinue = true; + switch (depth) { + case DEPTH_ZERO : + shouldContinue = false; + break; + case DEPTH_ONE : + shouldContinue = !path.equals(requestor.requestPath().removeLastSegments(1)); + break; + case DEPTH_INFINITE : + shouldContinue = true; + break; + } + return visitor.visit(proxy) && shouldContinue; + } catch (CoreException e) { + // Throw an exception to bail out of the traversal. + throw new WrappedRuntimeException(e); + } finally { + proxy.reset(); + } + } + }; + try { + new ElementTreeIterator(workspace.getElementTree(), getFullPath()).iterate(elementVisitor); + } catch (WrappedRuntimeException e) { + throw (CoreException) e.getTargetException(); + } finally { + proxy.requestor = null; + proxy.info = null; + } + } + + @Override + public void accept(IResourceVisitor visitor) throws CoreException { + accept(visitor, IResource.DEPTH_INFINITE, 0); + } + + @Override + public void accept(IResourceVisitor visitor, int depth, boolean includePhantoms) throws CoreException { + accept(visitor, depth, includePhantoms ? IContainer.INCLUDE_PHANTOMS : 0); + } + + @Override + public void accept(final IResourceVisitor visitor, int depth, int memberFlags) throws CoreException { + // Use the fast visitor if visiting to infinite depth. + if (depth == IResource.DEPTH_INFINITE) { + accept(new IResourceProxyVisitor() { + @Override + public boolean visit(IResourceProxy proxy) throws CoreException { + return visitor.visit(proxy.requestResource()); + } + }, memberFlags); + return; + } + // It is invalid to call accept on a phantom when INCLUDE_PHANTOMS is not specified. + final boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0; + ResourceInfo info = getResourceInfo(includePhantoms, false); + int flags = getFlags(info); + if ((memberFlags & IContainer.DO_NOT_CHECK_EXISTENCE) == 0) + checkAccessible(flags); + + // Check that this resource matches the member flags + if (!isMember(flags, memberFlags)) + return; + // Visit this resource. + if (!visitor.visit(this) || depth == DEPTH_ZERO) + return; + // Get the info again because it might have been changed by the visitor. + info = getResourceInfo(includePhantoms, false); + if (info == null) + return; + // Thread safety: (cache the type to avoid changes -- we might not be inside an operation). + int type = info.getType(); + if (type == FILE) + return; + // If we had a gender change we need to fix up the resource before asking for its members. + IContainer resource = getType() != type ? (IContainer) workspace.newResource(getFullPath(), type) : (IContainer) this; + IResource[] members = resource.members(memberFlags); + for (int i = 0; i < members.length; i++) + members[i].accept(visitor, DEPTH_ZERO, memberFlags | IContainer.DO_NOT_CHECK_EXISTENCE); + } + + protected void assertCopyRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + IStatus status = checkCopyRequirements(destination, destinationType, updateFlags); + if (!status.isOK()) { + // This assert is ok because the error cases generated by the check method above + // indicate assertion conditions. + Assert.isTrue(false, status.getChildren()[0].getMessage()); + } + } + + /** + * Throws an exception if the link preconditions are not met. Returns the file info + * for the file being linked to, or null if not available. + * @throws CoreException + */ + protected IFileInfo assertLinkRequirements(URI localLocation, int updateFlags) throws CoreException { + boolean allowMissingLocal = (updateFlags & IResource.ALLOW_MISSING_LOCAL) != 0; + if ((updateFlags & IResource.REPLACE) == 0) + checkDoesNotExist(getFlags(getResourceInfo(false, false)), true); + IStatus locationStatus = workspace.validateLinkLocationURI(this, localLocation); + // We only tolerate an undefined path variable in the allow missing local case. + final boolean variableUndefined = locationStatus.getCode() == IResourceStatus.VARIABLE_NOT_DEFINED_WARNING; + if (locationStatus.getSeverity() == IStatus.ERROR || (variableUndefined && !allowMissingLocal)) + throw new ResourceException(locationStatus); + // Check that the parent exists and is open. + Container parent = (Container) getParent(); + parent.checkAccessible(getFlags(parent.getResourceInfo(false, false))); + // If the variable is undefined we can't do any further checks. + if (variableUndefined) + return null; + // Check if the file exists. + URI resolved = getPathVariableManager().resolveURI(localLocation); + IFileStore store = EFS.getStore(resolved); + IFileInfo fileInfo = store.fetchInfo(); + boolean localExists = fileInfo.exists(); + if (!allowMissingLocal && !localExists) { + String msg = NLS.bind(Messages.links_localDoesNotExist, store.toString()); + throw new ResourceException(IResourceStatus.NOT_FOUND_LOCAL, getFullPath(), msg, null); + } + // Resource type and file system type must match. + if (localExists && ((getType() == IResource.FOLDER) != fileInfo.isDirectory())) { + String msg = NLS.bind(Messages.links_wrongLocalType, getFullPath()); + throw new ResourceException(IResourceStatus.WRONG_TYPE_LOCAL, getFullPath(), msg, null); + } + return fileInfo; + } + + protected void assertMoveRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + IStatus status = checkMoveRequirements(destination, destinationType, updateFlags); + if (!status.isOK()) { + // This assert is ok because the error cases generated by the check method + // above indicate assertion conditions. + Assert.isTrue(false, status.getChildren()[0].getMessage()); + } + } + + public void checkAccessible(int flags) throws CoreException { + checkExists(flags, true); + } + + private ResourceInfo checkAccessibleAndLocal(int depth) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + checkLocal(flags, depth); + return info; + } + + /** + * This method reports errors in two different ways. It can throw a + * CoreException or return a status. CoreExceptions are used according to the + * specification of the copy method. Programming errors, that would usually be + * prevented by using an "Assert" code, are reported as an IStatus. We're doing + * this way because we have two different methods to copy resources: + * IResource#copy and IWorkspace#copy. The first one gets the error and throws + * its message in an AssertionFailureException. The second one just throws a + * CoreException using the status returned by this method. + * + * @see IResource#copy(IPath, int, IProgressMonitor) + */ + public IStatus checkCopyRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + String message = Messages.resources_copyNotMet; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_VALUE, message, null); + if (destination == null) { + message = Messages.resources_destNotNull; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message); + } + destination = makePathAbsolute(destination); + if (getFullPath().isPrefixOf(destination)) { + message = NLS.bind(Messages.resources_copyDestNotSub, getFullPath()); + status.add(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message)); + } + checkValidPath(destination, destinationType, false); + + ResourceInfo info; + checkAccessibleAndLocal(DEPTH_INFINITE); + + IPath destinationParent = destination.removeLastSegments(1); + checkValidGroupContainer(destinationParent, isLinked(), isVirtual()); + + Resource dest = workspace.newResource(destination, destinationType); + dest.checkDoesNotExist(); + + // Ensure we aren't trying to copy a file to a project. + if (getType() == IResource.FILE && destinationType == IResource.PROJECT) { + message = Messages.resources_fileToProj; + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + + // We can't copy into a closed project. + if (destinationType != IResource.PROJECT) { + Project project = (Project) dest.getProject(); + info = project.getResourceInfo(false, false); + project.checkAccessible(getFlags(info)); + Container parent = (Container) dest.getParent(); + if (!parent.equals(project)) { + info = parent.getResourceInfo(false, false); + parent.checkExists(getFlags(info), true); + } + } + if (isUnderLink() || dest.isUnderLink()) { + // Make sure location is not null. This can occur with linked resources relative to + // undefined path variables + URI sourceLocation = getLocationURI(); + if (sourceLocation == null) { + message = NLS.bind(Messages.localstore_locationUndefined, getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, getFullPath(), message, null); + } + URI destLocation = dest.getLocationURI(); + if (destLocation == null && (dest.isUnderVirtual() == false)) { + message = NLS.bind(Messages.localstore_locationUndefined, dest.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, dest.getFullPath(), message, null); + } + // Make sure location of source is not a prefix of the location of the destination + // this can occur if the source and/or destination is a linked resource + if (getStore().isParentOf(dest.getStore())) { + message = NLS.bind(Messages.resources_copyDestNotSub, getFullPath()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + } + + return status.isOK() ? Status.OK_STATUS : (IStatus) status; + } + + /** + * Checks that this resource does not exist. If the file system is not case + * sensitive, this method also checks for a case variant. + */ + protected void checkDoesNotExist() throws CoreException { + checkDoesNotExist(getFlags(getResourceInfo(false, false)), false); + } + + /** + * Checks that this resource does not exist. If the file system is not case + * sensitive, this method also checks for a case variant. + * + * @exception CoreException if this resource exists + */ + public void checkDoesNotExist(int flags, boolean checkType) throws CoreException { + // If this exact resource exists we are done. + if (exists(flags, checkType)) { + String message = NLS.bind(Messages.resources_mustNotExist, getFullPath()); + throw new ResourceException(checkType ? IResourceStatus.RESOURCE_EXISTS : IResourceStatus.PATH_OCCUPIED, getFullPath(), message, null); + } + if (Workspace.caseSensitive) + return; + // Now look for a matching case variant in the tree. + IResource variant = findExistingResourceVariant(getFullPath()); + if (variant == null) + return; + String msg = NLS.bind(Messages.resources_existsDifferentCase, variant.getFullPath()); + throw new ResourceException(IResourceStatus.CASE_VARIANT_EXISTS, variant.getFullPath(), msg, null); + } + + /** + * Checks that this resource exists. + * If checkType is true, the type of this resource and the one in the tree must match. + * + * @exception CoreException if this resource does not exist + */ + public void checkExists(int flags, boolean checkType) throws CoreException { + if (!exists(flags, checkType)) { + String message = NLS.bind(Messages.resources_mustExist, getFullPath()); + throw new ResourceException(IResourceStatus.RESOURCE_NOT_FOUND, getFullPath(), message, null); + } + } + + /** + * Checks that this resource is local to the given depth. + * + * @exception CoreException if this resource is not local + */ + public void checkLocal(int flags, int depth) throws CoreException { + if (!isLocal(flags, depth)) { + String message = NLS.bind(Messages.resources_mustBeLocal, getFullPath()); + throw new ResourceException(IResourceStatus.RESOURCE_NOT_LOCAL, getFullPath(), message, null); + } + } + + /** + * This method reports errors in two different ways. It can throw a + * CoreException or log a status. CoreExceptions are used according + * to the specification of the move method. Programming errors, that + * would usually be prevented by using an "Assert" code, are reported as + * an IStatus. + * We're doing this way because we have two different methods to move + * resources: IResource#move and IWorkspace#move. The first one gets + * the error and throws its message in an AssertionFailureException. The + * second one just throws a CoreException using the status returned + * by this method. + * + * @see IResource#move(IPath, int, IProgressMonitor) + */ + protected IStatus checkMoveRequirements(IPath destination, int destinationType, int updateFlags) throws CoreException { + String message = Messages.resources_moveNotMet; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_VALUE, message, null); + if (destination == null) { + message = Messages.resources_destNotNull; + return new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message); + } + destination = makePathAbsolute(destination); + if (getFullPath().isPrefixOf(destination)) { + message = NLS.bind(Messages.resources_moveDestNotSub, getFullPath()); + status.add(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message)); + } + checkValidPath(destination, destinationType, false); + + ResourceInfo info; + checkAccessibleAndLocal(DEPTH_INFINITE); + + IPath destinationParent = destination.removeLastSegments(1); + checkValidGroupContainer(destinationParent, isLinked(), isVirtual()); + + Resource dest = workspace.newResource(destination, destinationType); + + // Check if we are only changing case. + IResource variant = Workspace.caseSensitive ? null : findExistingResourceVariant(destination); + if (variant == null || !this.equals(variant)) + dest.checkDoesNotExist(); + + // Ensure we aren't trying to move a file to a project. + if (getType() == IResource.FILE && dest.getType() == IResource.PROJECT) { + message = Messages.resources_fileToProj; + throw new ResourceException(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), message)); + } + + // We can't move into a closed project. + if (destinationType != IResource.PROJECT) { + Project project = (Project) dest.getProject(); + info = project.getResourceInfo(false, false); + project.checkAccessible(getFlags(info)); + Container parent = (Container) dest.getParent(); + if (!parent.equals(project)) { + info = parent.getResourceInfo(false, false); + parent.checkExists(getFlags(info), true); + } + } + if (isUnderLink() || dest.isUnderLink()) { + // Make sure location is not null. This can occur with linked resources relative to + // undefined path variables. + URI sourceLocation = getLocationURI(); + if (sourceLocation == null) { + message = NLS.bind(Messages.localstore_locationUndefined, getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, getFullPath(), message, null); + } + URI destLocation = dest.getLocationURI(); + if (destLocation == null && (dest.isUnderVirtual() == false)) { + message = NLS.bind(Messages.localstore_locationUndefined, dest.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, dest.getFullPath(), message, null); + } + // Make sure location of source is not a prefix of the location of the destination + // this can occur if the source and/or destination is a linked resource. + if (getStore().isParentOf(dest.getStore())) { + message = NLS.bind(Messages.resources_moveDestNotSub, getFullPath()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + } + + return status.isOK() ? Status.OK_STATUS : (IStatus) status; + } + + /** + * Checks that the supplied path is valid according to Workspace.validatePath(). + * + * @exception CoreException if the path is not valid + */ + public void checkValidPath(IPath toValidate, int type, boolean lastSegmentOnly) throws CoreException { + IStatus result = workspace.locationValidator.validatePath(toValidate, type, lastSegmentOnly); + if (!result.isOK()) + throw new ResourceException(result); + } + + /** + * Checks that the destination is a suitable one given that it could be a group. + * + * @exception CoreException if the path points to a group + */ + public void checkValidGroupContainer(IPath destination, boolean isLink, boolean isGroup) throws CoreException { + IStatus status = getValidGroupContainer(destination, isLink, isGroup); + if (!status.isOK()) + throw new ResourceException(status); + } + + /** + * Checks that the destination is a suitable one given that it could be a group. + * + * @exception CoreException if the path points to a group + */ + public void checkValidGroupContainer(Container destination, boolean isLink, boolean isGroup) throws CoreException { + if (!isLink && !isGroup) { + String message = Messages.group_invalidParent; + if (destination.isVirtual()) + throw new ResourceException(new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message)); + } + } + + public IStatus getValidGroupContainer(IPath destination, boolean isLink, boolean isGroup) { + if (!isLink && !isGroup) { + String message = Messages.group_invalidParent; + ResourceInfo info = workspace.getResourceInfo(destination, false, false); + if (info != null && info.isSet(M_VIRTUAL)) + return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message); + } + return Status.OK_STATUS; + } + + @Override + public void clearHistory(IProgressMonitor monitor) { + getLocalManager().getHistoryStore().remove(getFullPath(), monitor); + } + + @Override + public boolean contains(ISchedulingRule rule) { + if (this == rule) + return true; + // Must allow notifications to nest in all resource rules. + if (rule.getClass().equals(WorkManager.NotifyRule.class)) + return true; + if (rule instanceof MultiRule) { + MultiRule multi = (MultiRule) rule; + ISchedulingRule[] children = multi.getChildren(); + for (int i = 0; i < children.length; i++) + if (!contains(children[i])) + return false; + return true; + } + if (!(rule instanceof IResource)) + return false; + IResource resource = (IResource) rule; + if (!workspace.equals(resource.getWorkspace())) + return false; + return path.isPrefixOf(resource.getFullPath()); + } + + /** + * @throws CoreException + */ + public void convertToPhantom() throws CoreException { + ResourceInfo info = getResourceInfo(false, true); + if (info == null || isPhantom(getFlags(info))) + return; + info.clearSessionProperties(); + info.set(M_PHANTOM); + getLocalManager().updateLocalSync(info, I_NULL_SYNC_INFO); + info.clearModificationStamp(); + // Should already be done by the #deleteResource call but left in + // just to be safe and for code clarity. + info.setMarkers(null); + } + + @Override + public void copy(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + copy(destination, updateFlags, monitor); + } + + @Override + public void copy(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + String message = NLS.bind(Messages.resources_copying, getFullPath()); + SubMonitor progress = SubMonitor.convert(monitor, message, 100).checkCanceled(); + destination = makePathAbsolute(destination); + checkValidPath(destination, getType(), false); + Resource destResource = workspace.newResource(destination, getType()); + final ISchedulingRule rule = workspace.getRuleFactory().copyRule(this, destResource); + try { + workspace.prepareOperation(rule, progress.split(1)); + // The following assert method throws CoreExceptions as stated in the IResource.copy API + // and assert for programming errors. See checkCopyRequirements for more information. + assertCopyRequirements(destination, getType(), updateFlags); + workspace.beginOperation(true); + getLocalManager().copy(this, destResource, updateFlags, progress.split(98)); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true); + } + } + + @Override + public void copy(IProjectDescription destDesc, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + copy(destDesc, updateFlags, monitor); + } + + /** + * Used when a folder is to be copied to a project. + * @see IResource#copy(IProjectDescription, int, IProgressMonitor) + */ + @Override + public void copy(IProjectDescription destDesc, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(destDesc); + String message = NLS.bind(Messages.resources_copying, getFullPath()); + SubMonitor progress = SubMonitor.convert(monitor, message, 100).checkCanceled(); + try { + workspace.prepareOperation(workspace.getRoot(), progress.split(1)); + // The following assert method throws CoreExceptions as stated in the IResource.copy API + // and assert for programming errors. See checkCopyRequirements for more information. + IPath destPath = new Path(destDesc.getName()).makeAbsolute(); + assertCopyRequirements(destPath, getType(), updateFlags); + Project destProject = (Project) workspace.getRoot().getProject(destPath.lastSegment()); + workspace.beginOperation(true); + + // Create and open the new project. + destProject.create(destDesc, progress.split(5)); + destProject.open(progress.split(5)); + + // Copy the children. + IResource[] children = ((IContainer) this).members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + SubMonitor loopProgress = SubMonitor.convert(progress.split(70), children.length); + for (int i = 0; i < children.length; i++) { + Resource child = (Resource) children[i]; + child.copy(destPath.append(child.getName()), updateFlags, loopProgress.split(1)); + } + + // Copy over the properties. + getPropertyManager().copy(this, destProject, DEPTH_ZERO); + progress.split(18); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(workspace.getRoot(), true); + } + } + + /** + * Count the number of resources in the tree from this container to the + * specified depth. Include this resource. Include phantoms if + * the phantom boolean is true. + */ + public int countResources(int depth, boolean phantom) { + return workspace.countResources(path, depth, phantom); + } + + /** + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @see IFile#createLink(IPath, int, IProgressMonitor) + */ + public void createLink(IPath localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(localLocation); + createLink(URIUtil.toURI(localLocation), updateFlags, monitor); + } + + /** + * @see IFolder#createLink(URI, int, IProgressMonitor) + * @see IFile#createLink(URI, int, IProgressMonitor) + */ + public void createLink(URI localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(localLocation); + IResource existing = null; + if ((updateFlags & REPLACE) != 0) { + existing = workspace.getRoot().findMember(getFullPath()); + if (existing != null && existing.isLinked()) { + setLinkLocation(localLocation, updateFlags, monitor); + return; + } + } + + String message = NLS.bind(Messages.links_creating, getFullPath()); + SubMonitor progress = SubMonitor.convert(monitor, message, 100).checkCanceled(); + checkValidPath(path, FOLDER, true); + final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); + try { + workspace.prepareOperation(rule, progress.split(1)); + IFileInfo fileInfo = assertLinkRequirements(localLocation, updateFlags); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_CREATE, this)); + workspace.beginOperation(true); + // Replace existing resource, if applicable. + if ((updateFlags & REPLACE) != 0 && existing != null) { + workspace.deleteResource(existing); + } + ResourceInfo info = workspace.createResource(this, false); + if ((updateFlags & IResource.HIDDEN) != 0) + info.set(M_HIDDEN); + info.set(M_LINK); + LinkDescription linkDescription = new LinkDescription(this, localLocation); + if (linkDescription.isGroup()) + info.set(M_VIRTUAL); + getLocalManager().link(this, localLocation, fileInfo); + progress.split(5); + // Save the location in the project description. + Project project = (Project) getProject(); + boolean changed = project.internalGetDescription().setLinkLocation(getProjectRelativePath(), linkDescription); + if (changed) { + try { + project.writeDescription(IResource.NONE); + } catch (CoreException e) { + // A problem happened updating the description, so delete the resource from the workspace. + workspace.deleteResource(this); + throw e; // Rethrow. + } + } + progress.split(4); + + // 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); + progress.split(90); + } else { + refreshLocal(DEPTH_INFINITE, progress.split(90)); + } + } else { + progress.split(90); + } + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true); + } + } + + @Override + public IMarker createMarker(String type) throws CoreException { + Assert.isNotNull(type); + final ISchedulingRule rule = workspace.getRuleFactory().markerRule(this); + try { + workspace.prepareOperation(rule, null); + checkAccessible(getFlags(getResourceInfo(false, false))); + workspace.beginOperation(true); + MarkerInfo info = new MarkerInfo(); + info.setType(type); + info.setCreationTime(System.currentTimeMillis()); + workspace.getMarkerManager().add(this, info); + return new Marker(this, info.getId()); + } finally { + workspace.endOperation(rule, false); + } + } + + @Override + public IResourceProxy createProxy() { + ResourceProxy result = new ResourceProxy(); + result.info = getResourceInfo(false, false); + result.requestor = this; + result.resource = this; + return result; + } + + /** + * @see IProject#delete(boolean, boolean, IProgressMonitor) + * @see IWorkspaceRoot#delete(boolean, boolean, IProgressMonitor) + * N.B. This is not an IResource method! + */ + public void delete(boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; + delete(updateFlags, monitor); + } + + @Override + public void delete(boolean force, IProgressMonitor monitor) throws CoreException { + delete(force ? IResource.FORCE : IResource.NONE, monitor); + } + + @Override + public void delete(int updateFlags, IProgressMonitor monitor) throws CoreException { + String message = NLS.bind(Messages.resources_deleting, getFullPath()); + SubMonitor progress = SubMonitor.convert(monitor, 100).checkCanceled(); + progress.subTask(message); + final ISchedulingRule rule = workspace.getRuleFactory().deleteRule(this); + try { + workspace.prepareOperation(rule, progress.split(1)); + // If there is no resource then there is nothing to delete so just return. + if (!exists()) + return; + workspace.beginOperation(true); + broadcastPreDeleteEvent(); + + // When a project is being deleted, flush the build order in case there is a problem. + if (this.getType() == IResource.PROJECT) + workspace.flushBuildOrder(); + + final IFileStore originalStore = getStore(); + boolean wasLinked = isLinked(); + message = Messages.resources_deleteProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_DELETE_LOCAL, message, null); + WorkManager workManager = workspace.getWorkManager(); + ResourceTree tree = new ResourceTree(workspace.getFileSystemManager(), workManager.getLock(), status, updateFlags); + int depth = 0; + try { + depth = workManager.beginUnprotected(); + unprotectedDelete(tree, updateFlags, progress.split(50)); + } finally { + workManager.endUnprotected(depth); + } + if (getType() == ROOT) { + // Need to clear out the root info. + workspace.getMarkerManager().removeMarkers(this, IResource.DEPTH_ZERO); + getPropertyManager().deleteProperties(this, IResource.DEPTH_ZERO); + getResourceInfo(false, false).clearSessionProperties(); + } + // Invalidate the tree for further use by clients. + tree.makeInvalid(); + if (!tree.getStatus().isOK()) + throw new ResourceException(tree.getStatus()); + // Update any aliases of this resource. + // Note that deletion of a linked resource cannot affect other resources. + if (!wasLinked) + workspace.getAliasManager().updateAliases(this, originalStore, IResource.DEPTH_INFINITE, progress.split(48)); + if (getType() == PROJECT) { + // Make sure the rule factory is cleared on project deletion. + ((Rules) workspace.getRuleFactory()).setRuleFactory((IProject) this, null); + // Make sure project deletion is remembered. + workspace.getSaveManager().requestSnapshot(); + } + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true); + } + } + + @Override + public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException { + final ISchedulingRule rule = workspace.getRuleFactory().markerRule(this); + try { + workspace.prepareOperation(rule, null); + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + + workspace.beginOperation(true); + workspace.getMarkerManager().removeMarkers(this, type, includeSubtypes, depth); + } finally { + workspace.endOperation(rule, false); + } + } + + /** + * This method should be called to delete a resource from the tree because it will also + * delete its properties and markers. If a status object is provided, minor exceptions are + * added, otherwise they are thrown. If major exceptions occur, they are always thrown. + */ + public void deleteResource(boolean convertToPhantom, MultiStatus status) throws CoreException { + // Remove markers on this resource and its descendants. + if (exists()) + getMarkerManager().removeMarkers(this, IResource.DEPTH_INFINITE); + // If this is a linked resource or contains linked resources, + // remove their entries from the project description. + List links = findLinks(); + // Pre-delete notification to internal infrastructure + if (links != null) + for (Iterator it = links.iterator(); it.hasNext();) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_DELETE, it.next())); + + // Check if we deleted a preferences file. + ProjectPreferences.deleted(this); + + // Remove all deleted linked resources from the project description. + if (getType() != IResource.PROJECT && links != null) { + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + if (description != null) { + boolean wasChanged = false; + for (Iterator it = links.iterator(); it.hasNext();) + wasChanged |= description.setLinkLocation(it.next().getProjectRelativePath(), null); + if (wasChanged) { + project.internalSetDescription(description, true); + try { + project.writeDescription(IResource.FORCE); + } catch (CoreException e) { + // A problem happened updating the description, update the description in memory. + project.updateDescription(); + throw e; // Rethrow. + } + } + } + } + + // If we are synchronizing, do not delete the resource. Convert it + // into a phantom. Actual deletion will happen when we refresh or push. + if (convertToPhantom && getType() != PROJECT && synchronizing(getResourceInfo(true, false))) { + convertToPhantom(); + } else { + workspace.deleteResource(this); + } + + List filters = findFilters(); + if ((filters != null) && (filters.size() > 0)) { + // Delete resource filters. + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + if (description != null) { + for (Iterator it = filters.iterator(); it.hasNext();) + description.setFilters(it.next().getProjectRelativePath(), null); + project.internalSetDescription(description, true); + project.writeDescription(IResource.FORCE); + } + } + + // Delete properties after the resource is deleted from the tree. See bug 84584. + CoreException err = null; + try { + getPropertyManager().deleteResource(this); + } catch (CoreException e) { + if (status != null) { + status.add(e.getStatus()); + } else { + err = e; + } + } + if (err != null) + throw err; + } + + /** + * Returns a list of all linked resources at or below this resource, or null if there are no links. + */ + private List findLinks() { + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + HashMap linkMap = description.getLinks(); + if (linkMap == null) + return null; + List links = null; + IPath myPath = getProjectRelativePath(); + for (Iterator it = linkMap.values().iterator(); it.hasNext();) { + LinkDescription link = it.next(); + IPath linkPath = link.getProjectRelativePath(); + if (myPath.isPrefixOf(linkPath)) { + if (links == null) + links = new ArrayList<>(); + links.add(workspace.newResource(project.getFullPath().append(linkPath), link.getType())); + } + } + return links; + } + + /** + * Returns a list of all filtered resources at or below this resource, or null if there are no links. + */ + private List findFilters() { + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + List filters = null; + if (description != null) { + HashMap> filterMap = description.getFilters(); + if (filterMap != null) { + IPath myPath = getProjectRelativePath(); + for (Iterator it = filterMap.keySet().iterator(); it.hasNext();) { + IPath filterPath = it.next(); + if (myPath.isPrefixOf(filterPath)) { + if (filters == null) + filters = new ArrayList<>(); + filters.add(workspace.newResource(project.getFullPath().append(filterPath), IResource.FOLDER)); + } + } + } + } + return filters; + } + + @Override + public boolean equals(Object target) { + if (this == target) + return true; + if (!(target instanceof Resource)) + return false; + Resource resource = (Resource) target; + return getType() == resource.getType() && path.equals(resource.path) && workspace.equals(resource.workspace); + } + + @Override + public boolean exists() { + ResourceInfo info = getResourceInfo(false, false); + return exists(getFlags(info), true); + } + + public boolean exists(int flags, boolean checkType) { + return flags != NULL_FLAG && !(checkType && ResourceInfo.getType(flags) != getType()); + } + + /** + * Helper method for case insensitive file systems. Returns + * an existing resource whose path differs only in case from + * the given path, or null if no such resource exists. + */ + public IResource findExistingResourceVariant(IPath target) { + if (!workspace.tree.includesIgnoreCase(target)) + return null; + // Ignore phantoms. + ResourceInfo info = (ResourceInfo) workspace.tree.getElementDataIgnoreCase(target); + if (info != null && info.isSet(M_PHANTOM)) + return null; + // Resort to slow lookup to find exact case variant. + IPath result = Path.ROOT; + int segmentCount = target.segmentCount(); + for (int i = 0; i < segmentCount; i++) { + String[] childNames = workspace.tree.getNamesOfChildren(result); + String name = findVariant(target.segment(i), childNames); + if (name == null) + return null; + result = result.append(name); + } + return workspace.getRoot().findMember(result); + } + + @Override + public IMarker findMarker(long id) { + return workspace.getMarkerManager().findMarker(this, id); + } + + @Override + public IMarker[] findMarkers(String type, boolean includeSubtypes, int depth) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + // It might happen that from this point the resource is not accessible anymore. + // But markers have the #exists method that callers can use to check if it is still valid. + return workspace.getMarkerManager().findMarkers(this, type, includeSubtypes, depth); + } + + @Override + public int findMaxProblemSeverity(String type, boolean includeSubtypes, int depth) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + // It might happen that from this point the resource is not accessible anymore. + // But markers have the #exists method that callers can use to check if it is still valid. + return workspace.getMarkerManager().findMaxProblemSeverity(this, type, includeSubtypes, depth); + } + + /** + * Searches for a variant of the given target in the list, + * that differs only in case. Returns the variant from + * the list if one is found, otherwise returns null. + */ + private String findVariant(String target, String[] list) { + for (int i = 0; i < list.length; i++) { + if (target.toUpperCase().equals(list[i].toUpperCase())) + return list[i]; + } + return null; + } + + protected void fixupAfterMoveSource() throws CoreException { + ResourceInfo info = getResourceInfo(true, true); + // If a linked resource is moved, we need to remove the location info from the ".project" file. + if (isLinked() || isVirtual()) { + Project project = (Project) getProject(); + if (project.internalGetDescription().setLinkLocation(getProjectRelativePath(), null)) + project.writeDescription(IResource.NONE); + } + + List filters = findFilters(); + if ((filters != null) && (filters.size() > 0)) { + // Delete resource filters. + Project project = (Project) getProject(); + ProjectDescription description = project.internalGetDescription(); + for (Iterator it = filters.iterator(); it.hasNext();) + description.setFilters(it.next().getProjectRelativePath(), null); + project.writeDescription(IResource.NONE); + } + + // Check if we deleted a preferences file. + ProjectPreferences.deleted(this); + + if (!synchronizing(info)) { + workspace.deleteResource(this); + return; + } + info.clearSessionProperties(); + info.clear(M_LOCAL_EXISTS); + info.setLocalSyncInfo(I_NULL_SYNC_INFO); + info.set(M_PHANTOM); + info.clearModificationStamp(); + info.setMarkers(null); + } + + @Override + public String getFileExtension() { + String name = getName(); + int index = name.lastIndexOf('.'); + if (index == -1) + return null; + if (index == (name.length() - 1)) + return ""; //$NON-NLS-1$ + return name.substring(index + 1); + } + + public int getFlags(ResourceInfo info) { + return (info == null) ? NULL_FLAG : info.getFlags(); + } + + @Override + public IPath getFullPath() { + return path; + } + + public FileSystemResourceManager getLocalManager() { + return workspace.getFileSystemManager(); + } + + @Override + public long getLocalTimeStamp() { + ResourceInfo info = getResourceInfo(false, false); + return (info == null || isVirtual()) ? IResource.NULL_STAMP : info.getLocalSyncInfo(); + } + + @Override + public IPath getLocation() { + IProject project = getProject(); + if (project != null && !project.exists()) + return null; + return getLocalManager().locationFor(this, false); + } + + @Override + public URI getLocationURI() { + IProject project = getProject(); + if (project != null && !project.exists()) + return null; + return getLocalManager().locationURIFor(this, false); + } + + @Override + public IMarker getMarker(long id) { + return new Marker(this, id); + } + + protected MarkerManager getMarkerManager() { + return workspace.getMarkerManager(); + } + + @Override + public long getModificationStamp() { + ResourceInfo info = getResourceInfo(false, false); + return info == null ? IResource.NULL_STAMP : info.getModificationStamp(); + } + + @Override + public String getName() { + return path.lastSegment(); + } + + @Override + public IContainer getParent() { + int segments = path.segmentCount(); + // Zero and one segments handled by subclasses. + if (segments < 2) + Assert.isLegal(false, path.toString()); + if (segments == 2) + return workspace.getRoot().getProject(path.segment(0)); + return (IFolder) workspace.newResource(path.removeLastSegments(1), IResource.FOLDER); + } + + @Override + public String getPersistentProperty(QualifiedName key) throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + return getPropertyManager().getProperty(this, key); + } + + @Override + public Map getPersistentProperties() throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + return getPropertyManager().getProperties(this); + } + + @Override + public IProject getProject() { + return workspace.getRoot().getProject(path.segment(0)); + } + + @Override + public IPath getProjectRelativePath() { + return getFullPath().removeFirstSegments(ICoreConstants.PROJECT_SEGMENT_LENGTH); + } + + public IPropertyManager getPropertyManager() { + return workspace.getPropertyManager(); + } + + @Override + public IPath getRawLocation() { + if (isLinked()) + return FileUtil.toPath(((Project) getProject()).internalGetDescription().getLinkLocationURI(getProjectRelativePath())); + return getLocation(); + } + + @Override + public URI getRawLocationURI() { + if (isLinked()) + return ((Project) getProject()).internalGetDescription().getLinkLocationURI(getProjectRelativePath()); + return getLocationURI(); + } + + @Override + public ResourceAttributes getResourceAttributes() { + if (!isAccessible() || isVirtual()) + return null; + return getLocalManager().attributes(this); + } + + /** + * Returns the resource info. Returns null if the resource doesn't exist. + * If the phantom flag is true, phantom resources are considered. + * If the mutable flag is true, a mutable info is returned. + */ + public ResourceInfo getResourceInfo(boolean phantom, boolean mutable) { + return workspace.getResourceInfo(getFullPath(), phantom, mutable); + } + + @Override + public Object getSessionProperty(QualifiedName key) throws CoreException { + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + return info.getSessionProperty(key); + } + + @Override + public Map getSessionProperties() throws CoreException { + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + return info.getSessionProperties(); + } + + public IFileStore getStore() { + return getLocalManager().getStore(this); + } + + @Override + public abstract int getType(); + + public String getTypeString() { + switch (getType()) { + case FILE : + return "L"; //$NON-NLS-1$ + case FOLDER : + return "F"; //$NON-NLS-1$ + case PROJECT : + return "P"; //$NON-NLS-1$ + case ROOT : + return "R"; //$NON-NLS-1$ + } + return ""; //$NON-NLS-1$ + } + + @Override + public IWorkspace getWorkspace() { + return workspace; + } + + @Override + public int hashCode() { + // The container may be null if the identified resource + // does not exist so don't bother with it in the hash + return getFullPath().hashCode(); + } + + /** + * Sets the M_LOCAL_EXISTS flag. Is internal so we don't have + * to begin an operation. + */ + protected void internalSetLocal(boolean flag, int depth) throws CoreException { + ResourceInfo info = getResourceInfo(true, true); + // Only make the change if it's not already in desired state. + if (info.isSet(M_LOCAL_EXISTS) != flag) { + if (flag && !isPhantom(getFlags(info))) { + info.set(M_LOCAL_EXISTS); + workspace.updateModificationStamp(info); + } else { + info.clear(M_LOCAL_EXISTS); + info.clearModificationStamp(); + } + } + if (getType() == IResource.FILE || depth == IResource.DEPTH_ZERO) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.DEPTH_ZERO; + IResource[] children = ((IContainer) this).members(); + for (int i = 0; i < children.length; i++) + ((Resource) children[i]).internalSetLocal(flag, depth); + } + + @Override + public boolean isAccessible() { + return exists(); + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + if (this == rule) + return true; + // Must not schedule at same time as notification. + if (rule.getClass().equals(WorkManager.NotifyRule.class)) + return true; + if (rule instanceof MultiRule) { + MultiRule multi = (MultiRule) rule; + ISchedulingRule[] children = multi.getChildren(); + for (int i = 0; i < children.length; i++) + if (isConflicting(children[i])) + return true; + return false; + } + if (!(rule instanceof IResource)) + return false; + IResource resource = (IResource) rule; + if (!workspace.equals(resource.getWorkspace())) + return false; + IPath otherPath = resource.getFullPath(); + return path.isPrefixOf(otherPath) || otherPath.isPrefixOf(path); + } + + @Override + public boolean isDerived() { + return isDerived(IResource.NONE); + } + + @Override + public boolean isDerived(int options) { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_DERIVED)) + return true; + // Check ancestors if the appropriate option is set. + if ((options & CHECK_ANCESTORS) != 0) + return getParent().isDerived(options); + return false; + } + + @Override + public boolean isHidden() { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_HIDDEN); + } + + @Override + public boolean isHidden(int options) { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_HIDDEN)) + return true; + // Check ancestors if the appropriate option is set. + if ((options & CHECK_ANCESTORS) != 0) + return getParent().isHidden(options); + return false; + } + + @Override + public boolean isLinked() { + return isLinked(NONE); + } + + @Override + public boolean isLinked(int options) { + if ((options & CHECK_ANCESTORS) != 0) { + IProject project = getProject(); + if (project == null) + return false; + ProjectDescription desc = ((Project) project).internalGetDescription(); + if (desc == null) + return false; + HashMap links = desc.getLinks(); + if (links == null) + return false; + IPath myPath = getProjectRelativePath(); + for (Iterator it = links.values().iterator(); it.hasNext();) { + if (it.next().getProjectRelativePath().isPrefixOf(myPath)) + return true; + } + return false; + } + // The no ancestor checking case. + ResourceInfo info = getResourceInfo(false, false); + return info != null && info.isSet(M_LINK); + } + + @Override + public boolean isVirtual() { + ResourceInfo info = getResourceInfo(false, false); + return info != null && info.isSet(M_VIRTUAL); + } + + /** + * Checks whether the current resource has a parent that is virtual. + */ + public boolean isUnderVirtual() { + IContainer parent = getParent(); + while (parent != null) { + if (parent.isVirtual()) + return true; + parent = parent.getParent(); + } + return false; + } + + @Override + @Deprecated + public boolean isLocal(int depth) { + ResourceInfo info = getResourceInfo(false, false); + return isLocal(getFlags(info), depth); + } + + /** + * Note the depth parameter is intentionally ignored because + * this method is over-ridden by {@link Container#isLocal(int)}. + * @deprecated + */ + @Deprecated + public boolean isLocal(int flags, int depth) { + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_LOCAL_EXISTS); + } + + /** + * Returns whether a resource should be included in a traversal + * based on the provided member flags. + * + * @param flags The resource info flags + * @param memberFlags The member flag mask + * @return Whether the resource is included + */ + protected boolean isMember(int flags, int memberFlags) { + int excludeMask = 0; + if ((memberFlags & IContainer.INCLUDE_PHANTOMS) == 0) + excludeMask |= M_PHANTOM; + if ((memberFlags & IContainer.INCLUDE_HIDDEN) == 0) + excludeMask |= M_HIDDEN; + if ((memberFlags & IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS) == 0) + excludeMask |= M_TEAM_PRIVATE_MEMBER; + if ((memberFlags & IContainer.EXCLUDE_DERIVED) != 0) + excludeMask |= M_DERIVED; + // The resource is a matching member if it matches none of the exclude flags. + return flags != NULL_FLAG && (flags & excludeMask) == 0; + } + + @Override + public boolean isPhantom() { + ResourceInfo info = getResourceInfo(true, false); + return isPhantom(getFlags(info)); + } + + public boolean isPhantom(int flags) { + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_PHANTOM); + } + + @Deprecated + @Override + public boolean isReadOnly() { + final ResourceAttributes attributes = getResourceAttributes(); + return attributes != null && attributes.isReadOnly(); + } + + @Override + public boolean isSynchronized(int depth) { + return getLocalManager().isSynchronized(this, depth); + } + + @Override + public boolean isTeamPrivateMember() { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_TEAM_PRIVATE_MEMBER); + } + + @Override + public boolean isTeamPrivateMember(int options) { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + if (flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_TEAM_PRIVATE_MEMBER)) + return true; + // Check ancestors if the appropriate option is set. + if ((options & CHECK_ANCESTORS) != 0) + return getParent().isTeamPrivateMember(options); + return false; + } + + /** + * Returns true if this resource is a linked resource, or a child of a linked + * resource, and false otherwise. + */ + public boolean isUnderLink() { + int depth = path.segmentCount(); + if (depth < 2) + return false; + if (depth == 2) + return isLinked(); + // Check if parent at depth two is a link. + IPath linkParent = path.removeLastSegments(depth - 2); + return workspace.getResourceInfo(linkParent, false, false).isSet(ICoreConstants.M_LINK); + } + + protected IPath makePathAbsolute(IPath target) { + if (target.isAbsolute()) + return target; + return getParent().getFullPath().append(target); + } + + /** + * @see IFile#move(IPath, boolean, boolean, IProgressMonitor) + * @see IFolder#move(IPath, boolean, boolean, IProgressMonitor) + */ + public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; + move(destination, updateFlags, monitor); + } + + @Override + public void move(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + move(destination, force ? IResource.FORCE : IResource.NONE, monitor); + } + + @Override + public void move(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + String message = NLS.bind(Messages.resources_moving, getFullPath()); + SubMonitor progress = SubMonitor.convert(monitor, message, 100).checkCanceled(); + destination = makePathAbsolute(destination); + checkValidPath(destination, getType(), false); + Resource destResource = workspace.newResource(destination, getType()); + final ISchedulingRule rule = workspace.getRuleFactory().moveRule(this, destResource); + try { + workspace.prepareOperation(rule, progress.split(1)); + // The following assert method throws CoreExceptions as stated in the IResource.move API + // and assert for programming errors. See checkMoveRequirements for more information. + assertMoveRequirements(destination, getType(), updateFlags); + workspace.beginOperation(true); + broadcastPreMoveEvent(destResource, updateFlags); + IFileStore originalStore = getStore(); + message = Messages.resources_moveProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, null); + WorkManager workManager = workspace.getWorkManager(); + ResourceTree tree = new ResourceTree(workspace.getFileSystemManager(), workManager.getLock(), status, updateFlags); + boolean success = false; + int depth = 0; + try { + depth = workManager.beginUnprotected(); + success = unprotectedMove(tree, destResource, updateFlags, progress.split(50)); + } finally { + workManager.endUnprotected(depth); + } + // Invalidate the tree for further use by clients. + tree.makeInvalid(); + // Update any aliases of this resource and the destination. + if (success) { + workspace.getAliasManager().updateAliases(this, originalStore, IResource.DEPTH_INFINITE, progress.split(24)); + workspace.getAliasManager().updateAliases(destResource, destResource.getStore(), IResource.DEPTH_INFINITE, progress.split(24)); + } + if (!tree.getStatus().isOK()) + throw new ResourceException(tree.getStatus()); + // If this is a project, make sure the move operation is remembered. + if (getType() == PROJECT) + workspace.getSaveManager().requestSnapshot(); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true); + } + } + + @Override + public void move(IProjectDescription description, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= keepHistory ? IResource.KEEP_HISTORY : IResource.NONE; + move(description, updateFlags, monitor); + } + + @Override + public void move(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(description); + if (getType() != IResource.PROJECT) { + String message = NLS.bind(Messages.resources_moveNotProject, getFullPath(), description.getName()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + ((Project) this).move(description, updateFlags, monitor); + } + + @Override + public void refreshLocal(int depth, IProgressMonitor monitor) throws CoreException { + boolean isRoot = getType() == ROOT; + String message = isRoot ? Messages.resources_refreshingRoot : NLS.bind(Messages.resources_refreshing, getFullPath()); + SubMonitor progress = SubMonitor.convert(monitor, 100).checkCanceled(); + progress.subTask(message); + boolean build = false; + final ISchedulingRule rule = workspace.getRuleFactory().refreshRule(this); + try { + workspace.prepareOperation(rule, progress.split(1)); + if (!isRoot && !getProject().isAccessible()) + return; + if (!exists() && isFiltered()) + return; + workspace.beginOperation(true); + if (getType() == IResource.PROJECT || getType() == IResource.ROOT) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_REFRESH, this)); + build = getLocalManager().refresh(this, depth, true, progress.split(98)); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, build); + } + } + + @Override + public String requestName() { + return getName(); + } + + @Override + public IPath requestPath() { + return getFullPath(); + } + + @Override + public void revertModificationStamp(long value) throws CoreException { + if (value < 0) + throw new IllegalArgumentException("Illegal value: " + value); //$NON-NLS-1$ + // Fetch the info but don't bother making it mutable even though we are going to modify it. + // It really doesn't matter as the change we are making does not show up in deltas. + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + info.setModificationStamp(value); + } + + @Deprecated + @Override + public void setDerived(boolean isDerived) throws CoreException { + // Fetch the info but don't bother making it mutable even though we are going + // to modify it. We don't know whether or not the tree is open and it really doesn't + // matter as the change we are making does not show up in deltas. + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + // Ignore attempts to set derived flag on anything except files and folders + if (info.getType() == FILE || info.getType() == FOLDER) { + if (isDerived) { + info.set(ICoreConstants.M_DERIVED); + } else { + info.clear(ICoreConstants.M_DERIVED); + } + } + } + + @Override + public void setDerived(boolean isDerived, IProgressMonitor monitor) throws CoreException { + String message = NLS.bind(Messages.resources_settingDerivedFlag, getFullPath()); + SubMonitor progress = SubMonitor.convert(monitor, message, 100).checkCanceled(); + final ISchedulingRule rule = workspace.getRuleFactory().derivedRule(this); + try { + workspace.prepareOperation(rule, progress.split(1)); + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + // Ignore attempts to set derived flag on anything except files and folders. + if (info.getType() != FILE && info.getType() != FOLDER) + return; + workspace.beginOperation(true); + info = getResourceInfo(false, true); + if (isDerived) { + info.set(ICoreConstants.M_DERIVED); + } else { + info.clear(ICoreConstants.M_DERIVED); + } + progress.split(98); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true); + } + } + + @Override + public void setHidden(boolean isHidden) throws CoreException { + // Fetch the info but don't bother making it mutable even though we are going + // to modify it. We don't know whether or not the tree is open and it really doesn't + // matter as the change we are making does not show up in deltas. + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + if (isHidden) { + info.set(ICoreConstants.M_HIDDEN); + } else { + info.clear(ICoreConstants.M_HIDDEN); + } + } + + @Deprecated + @Override + public void setLocal(boolean flag, int depth, IProgressMonitor monitor) throws CoreException { + String message = Messages.resources_setLocal; + SubMonitor progress = SubMonitor.convert(monitor, message, 100).checkCanceled(); + try { + workspace.prepareOperation(null, progress.split(1)); + workspace.beginOperation(true); + internalSetLocal(flag, depth); + progress.split(98); + } finally { + workspace.endOperation(null, true); + } + } + + @Override + public long setLocalTimeStamp(long value) throws CoreException { + if (value < 0) + throw new IllegalArgumentException("Illegal value: " + value); //$NON-NLS-1$ + // Fetch the info but don't bother making it mutable even though we are going to modify it. + // It really doesn't matter as the change we are making does not show up in deltas. + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + return getLocalManager().setLocalTimeStamp(this, info, value); + } + + @Override + public void setPersistentProperty(QualifiedName key, String value) throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + getPropertyManager().setProperty(this, key, value); + } + + @Deprecated + @Override + public void setReadOnly(boolean readonly) { + ResourceAttributes attributes = getResourceAttributes(); + if (attributes == null) + return; + attributes.setReadOnly(readonly); + try { + setResourceAttributes(attributes); + } catch (CoreException e) { + // Failure is not an option. + } + } + + @Override + public void setResourceAttributes(ResourceAttributes attributes) throws CoreException { + checkAccessibleAndLocal(DEPTH_ZERO); + getLocalManager().setResourceAttributes(this, attributes); + } + + @Override + public void setSessionProperty(QualifiedName key, Object value) throws CoreException { + // Fetch the info but don't bother making it mutable even though we are going to modify it. + // We don't know whether or not the tree is open and it really doesn't matter as the change + // we are making does not show up in deltas. + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + info.setSessionProperty(key, value); + } + + @Override + public void setTeamPrivateMember(boolean isTeamPrivate) throws CoreException { + // Fetch the info but don't bother making it mutable even though we are going + // to modify it. We don't know whether or not the tree is open and it really doesn't + // matter as the change we are making does not show up in deltas. + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + // ignore attempts to set team private member flag on anything except files and folders + if (info.getType() == FILE || info.getType() == FOLDER) { + if (isTeamPrivate) { + info.set(ICoreConstants.M_TEAM_PRIVATE_MEMBER); + } else { + info.clear(ICoreConstants.M_TEAM_PRIVATE_MEMBER); + } + } + } + + /** + * Returns true if this resource has the potential to be + * (or have been) synchronized. + */ + public boolean synchronizing(ResourceInfo info) { + return info != null && info.getSyncInfo(false) != null; + } + + @Override + public String toString() { + return getTypeString() + getFullPath().toString(); + } + + @Override + public void touch(IProgressMonitor monitor) throws CoreException { + String message = NLS.bind(Messages.resources_touch, getFullPath()); + SubMonitor progress = SubMonitor.convert(monitor, message, 100).checkCanceled(); + final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); + try { + workspace.prepareOperation(rule, progress.split(1)); + ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO); + + workspace.beginOperation(true); + // Fake a change by incrementing the content ID. + info = getResourceInfo(false, true); + info.incrementContentId(); + // Forget content-related caching flags. + info.clear(M_CONTENT_CACHE); + workspace.updateModificationStamp(info); + progress.split(98); + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, true); + } + } + + /** + * Calls the move/delete hook to perform the deletion. Since this method calls + * client code, it is run "unprotected", so the workspace lock is not held. + */ + private void unprotectedDelete(ResourceTree tree, int updateFlags, IProgressMonitor monitor) { + IMoveDeleteHook hook = workspace.getMoveDeleteHook(); + SubMonitor progress = SubMonitor.convert(monitor, 2).checkCanceled(); + switch (getType()) { + case IResource.FILE : + if (!hook.deleteFile(tree, (IFile) this, updateFlags, progress.split(1))) + tree.standardDeleteFile((IFile) this, updateFlags, progress.split(1)); + break; + case IResource.FOLDER : + if (!hook.deleteFolder(tree, (IFolder) this, updateFlags, progress.split(1))) + tree.standardDeleteFolder((IFolder) this, updateFlags, progress.split(1)); + break; + case IResource.PROJECT : + if (!hook.deleteProject(tree, (IProject) this, updateFlags, progress.split(1))) + tree.standardDeleteProject((IProject) this, updateFlags, progress.split(1)); + break; + case IResource.ROOT : + // When the root is deleted, all its children including hidden projects have to be deleted. + IProject[] projects = ((IWorkspaceRoot) this).getProjects(IContainer.INCLUDE_HIDDEN); + progress.setWorkRemaining(projects.length * 2); + for (IProject project : projects) { + if (!hook.deleteProject(tree, project, updateFlags, progress.split(1))) + tree.standardDeleteProject(project, updateFlags, progress.split(1)); + } + break; + } + } + + /** + * Calls the move/delete hook to perform the move. Since this method calls + * client code, it is run "unprotected", so the workspace lock is not held. + * Returns true if resources were actually moved, and false otherwise. + */ + private boolean unprotectedMove(ResourceTree tree, final IResource destination, int updateFlags, IProgressMonitor monitor) throws CoreException, ResourceException { + IMoveDeleteHook hook = workspace.getMoveDeleteHook(); + SubMonitor progress = SubMonitor.convert(monitor, 2).checkCanceled(); + switch (getType()) { + case IResource.FILE : + if (!hook.moveFile(tree, (IFile) this, (IFile) destination, updateFlags, progress.split(1))) + tree.standardMoveFile((IFile) this, (IFile) destination, updateFlags, progress.split(1)); + break; + case IResource.FOLDER : + if (!hook.moveFolder(tree, (IFolder) this, (IFolder) destination, updateFlags, progress.split(1))) + tree.standardMoveFolder((IFolder) this, (IFolder) destination, updateFlags, progress.split(1)); + break; + case IResource.PROJECT : + IProject project = (IProject) this; + // If there is no change in name, there is nothing to do so return. + if (getName().equals(destination.getName())) + return false; + IProjectDescription description = project.getDescription(); + description.setName(destination.getName()); + if (!hook.moveProject(tree, project, description, updateFlags, progress.split(1))) + tree.standardMoveProject(project, description, updateFlags, progress.split(1)); + break; + case IResource.ROOT : + String msg = Messages.resources_moveRoot; + throw new ResourceException(new ResourceStatus(IResourceStatus.INVALID_VALUE, getFullPath(), msg)); + } + return true; + } + + private void broadcastPreDeleteEvent() throws CoreException { + switch (getType()) { + case IResource.PROJECT : + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_DELETE, this)); + break; + case IResource.ROOT : + // All root children including hidden projects will be deleted so notify. + IResource[] projects = ((Container) this).getChildren(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) { + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_DELETE, projects[i])); + } + break; + } + } + + private void broadcastPreMoveEvent(final IResource destination, int updateFlags) throws CoreException { + switch (getType()) { + case IResource.FILE : + if (isLinked()) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_MOVE, this, destination, updateFlags)); + break; + case IResource.FOLDER : + if (isLinked()) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_MOVE, this, destination, updateFlags)); + if (isVirtual()) + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_GROUP_MOVE, this, destination, updateFlags)); + break; + case IResource.PROJECT : + if (!getName().equals(destination.getName())) { + // If there is a change in name, we are deleting the source project so notify. + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_MOVE, this, destination, updateFlags)); + } + break; + } + } + + @Override + public IPathVariableManager getPathVariableManager() { + if (getProject() == null) + return workspace.getPathVariableManager(); + return new ProjectPathVariableManager(this); + } + + /** + * Calculates whether the current resource is filtered out from the resource tree + * by resource filters. This can happen because resource filters apply to the resource, + * or because resource filters apply to one of its parent. For example, if "/foo/bar" + * is filtered out, then calling isFilteredFromParent() on "/foo/bar/sub/file.txt" will + * return true as well, even though there's no resource filters that apply to "file.txt" per se. + * + * @return true is the resource is filtered out from the resource tree + * @see IContainer#createFilter(int, FileInfoMatcherDescription, int, IProgressMonitor) + */ + public boolean isFiltered() { + try { + return isFilteredWithException(false); + } catch (CoreException e) { + // nothing + } + return false; + } + + public boolean isFilteredWithException(boolean throwExeception) throws CoreException { + if (isLinked() || isVirtual()) + return false; + + Project project = (Project) getProject(); + if (project == null) + return false; + final ProjectDescription description = project.internalGetDescription(); + if (description == null) + return false; + if (description.getFilters() == null) + return false; + + Resource currentResource = this; + while (currentResource != null && currentResource.getParent() != null) { + Resource parent = (Resource) currentResource.getParent(); + IFileStore store = currentResource.getStore(); + if (store != null) { + FileInfo fileInfo = new FileInfo(store.getName()); + fileInfo.setDirectory(currentResource.getType() == IResource.FOLDER); + if (fileInfo != null) { + IFileInfo[] filtered = parent.filterChildren(project, description, new IFileInfo[] {fileInfo}, throwExeception); + if (filtered.length == 0) + return true; + } + } + currentResource = parent; + } + return false; + } + + public IFileInfo[] filterChildren(IFileInfo[] list, boolean throwException) throws CoreException { + Project project = (Project) getProject(); + if (project == null) + return list; + final ProjectDescription description = project.internalGetDescription(); + if (description == null) + return list; + return filterChildren(project, description, list, throwException); + } + + private IFileInfo[] filterChildren(Project project, ProjectDescription description, IFileInfo[] list, boolean throwException) throws CoreException { + IPath relativePath = getProjectRelativePath(); + LinkedList currentIncludeFilters = new LinkedList<>(); + LinkedList currentExcludeFilters = new LinkedList<>(); + LinkedList filters = null; + + boolean firstSegment = true; + do { + if (!firstSegment) + relativePath = relativePath.removeLastSegments(1); + filters = description.getFilter(relativePath); + if (filters != null) { + for (Iterator it = filters.iterator(); it.hasNext();) { + FilterDescription desc = it.next(); + if (firstSegment || desc.isInheritable()) { + Filter filter = new Filter(project, desc); + if (filter.isIncludeOnly()) { + if (filter.isFirst()) + currentIncludeFilters.addFirst(filter); + else + currentIncludeFilters.addLast(filter); + } else { + if (filter.isFirst()) + currentExcludeFilters.addFirst(filter); + else + currentExcludeFilters.addLast(filter); + } + } + } + } + firstSegment = false; + } while (relativePath.segmentCount() > 0); + + if ((currentIncludeFilters.size() > 0) || (currentExcludeFilters.size() > 0)) { + try { + list = Filter.filter(project, currentIncludeFilters, currentExcludeFilters, (IContainer) this, list); + } catch (CoreException e) { + if (throwException) + throw e; + } + } + return list; + } + + /** + * @see IFolder#createLink(URI, int, IProgressMonitor) + * @see IFile#createLink(URI, int, IProgressMonitor) + */ + public void setLinkLocation(URI location, int updateFlags, IProgressMonitor monitor) throws CoreException { + if (!isLinked()) { + String message = NLS.bind(Messages.links_resourceIsNotALink, getFullPath()); + throw new ResourceException(IResourceStatus.INVALID_VALUE, getFullPath(), message, null); + } + + String message = NLS.bind(Messages.links_setLocation, getFullPath()); + SubMonitor progress = SubMonitor.convert(monitor, message, 100).checkCanceled(); + final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); + try { + workspace.prepareOperation(rule, progress.split(1)); + workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_LINK_CHANGE, this)); + workspace.beginOperation(true); + + ResourceInfo info = workspace.getResourceInfo(getFullPath(), true, false); + getLocalManager().setLocation(this, info, location); + + LinkDescription linkDescription; + linkDescription = new LinkDescription(this, location); + Project project = (Project) getProject(); + project.internalGetDescription().setLinkLocation(getProjectRelativePath(), linkDescription); + project.writeDescription(updateFlags); + + // Refresh either in background or foreground. + if ((updateFlags & IResource.BACKGROUND_REFRESH) != 0) { + workspace.refreshManager.refresh(this); + progress.split(99); + } else { + refreshLocal(DEPTH_INFINITE, progress.split(98)); + } + } finally { + workspace.endOperation(rule, true); + } + } + + /** + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @see IFile#createLink(IPath, int, IProgressMonitor) + */ + public void setLinkLocation(IPath location, int updateFlags, IProgressMonitor monitor) throws CoreException { + if (location.isAbsolute()) { + setLinkLocation(URIUtil.toURI(location.toPortableString()), updateFlags, monitor); + } else { + URI uri; + try { + uri = new URI(null, null, location.toPortableString(), null); + } catch (URISyntaxException e) { + uri = URIUtil.toURI(location.toPortableString()); + } + setLinkLocation(uri, updateFlags, monitor); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java new file mode 100644 index 0000000000..288ac8c454 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * 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 java.io.PrintStream; +import java.io.PrintWriter; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; + +/** + * A checked exception representing a failure. + *

                  + * Resource exceptions contain a status object describing the cause of the + * exception, and optionally the path of the resource where the failure + * occurred. + *

                  + * + * @see IStatus + */ +public class ResourceException extends CoreException { + /** + * All serializable objects should have a stable serialVersionUID + */ + private static final long serialVersionUID = 1L; + + public ResourceException(int code, IPath path, String message, Throwable exception) { + super(new ResourceStatus(code, path, message, exception)); + } + + /** + * Constructs a new exception with the given status object. + * + * @param status the status object to be associated with this exception + * @see IStatus + */ + public ResourceException(IStatus status) { + super(status); + } + + /** + * Prints a stack trace out for the exception, and + * any nested exception that it may have embedded in + * its Status object. + */ + @Override + public void printStackTrace() { + printStackTrace(System.err); + } + + /** + * Prints a stack trace out for the exception, and + * any nested exception that it may have embedded in + * its Status object. + */ + @Override + public void printStackTrace(PrintStream output) { + synchronized (output) { + IStatus status = getStatus(); + if (status.getException() != null) { + String path = "()"; //$NON-NLS-1$ + if (status instanceof IResourceStatus) + path = "(" + ((IResourceStatus) status).getPath() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + output.print(getClass().getName() + path + "[" + status.getCode() + "]: "); //$NON-NLS-1$ //$NON-NLS-2$ + status.getException().printStackTrace(output); + } else + super.printStackTrace(output); + } + } + + /** + * Prints a stack trace out for the exception, and + * any nested exception that it may have embedded in + * its Status object. + */ + @Override + public void printStackTrace(PrintWriter output) { + synchronized (output) { + IStatus status = getStatus(); + if (status.getException() != null) { + String path = "()"; //$NON-NLS-1$ + if (status instanceof IResourceStatus) + path = "(" + ((IResourceStatus) status).getPath() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + output.print(getClass().getName() + path + "[" + status.getCode() + "]: "); //$NON-NLS-1$ //$NON-NLS-2$ + status.getException().printStackTrace(output); + } else + super.printStackTrace(output); + } + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java new file mode 100644 index 0000000000..a421544944 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java @@ -0,0 +1,477 @@ +/******************************************************************************* + * 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 + * Oakland Software Incorporated - added getSessionProperties and getPersistentProperties + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.Map; +import org.eclipse.core.internal.localstore.FileStoreRoot; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.internal.watson.IElementTreeData; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.QualifiedName; + +/** + * A data structure containing the in-memory state of a resource in the workspace. + */ +public class ResourceInfo implements IElementTreeData, ICoreConstants, IStringPoolParticipant { + protected static final int LOWER = 0xFFFF; + protected static final int UPPER = 0xFFFF0000; + + /** + * This field stores the resource modification stamp in the lower two bytes, + * and the character set generation count in the higher two bytes. + */ + protected volatile int charsetAndContentId = 0; + + /** + * The file system root that this resource is stored in + */ + protected FileStoreRoot fileStoreRoot; + + /** Set of flags which reflect various states of the info (used, derived, ...). */ + protected int flags = 0; + + /** Local sync info */ + // thread safety: (Concurrency004) + protected volatile long localInfo = I_NULL_SYNC_INFO; + + /** + * This field stores the sync info generation in the lower two bytes, and + * the marker generation count in the upper two bytes. + */ + protected volatile int markerAndSyncStamp; + + /** The collection of markers for this resource. */ + protected MarkerSet markers = null; + + /** Modification stamp */ + protected long modStamp = 0; + + /** Unique node identifier */ + // thread safety: (Concurrency004) + protected volatile long nodeId = 0; + + /** + * The properties which are maintained for the lifecycle of the workspace. + *

                  + * This field is declared as the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + protected ObjectMap sessionProperties = null; + + /** + * The table of sync information. + *

                  + * This field is declared as the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + protected ObjectMap syncInfo = null; + + /** + * Returns the integer value stored in the indicated part of this info's flags. + */ + protected static int getBits(int flags, int mask, int start) { + return (flags & mask) >> start; + } + + /** + * Returns the type setting for this info. Valid values are + * FILE, FOLDER, PROJECT, + */ + public static int getType(int flags) { + return getBits(flags, M_TYPE, M_TYPE_START); + } + + /** + * Returns true if all of the bits indicated by the mask are set. + */ + public static boolean isSet(int flags, int mask) { + return (flags & mask) == mask; + } + + /** + * Clears all of the bits indicated by the mask. + */ + public void clear(int mask) { + flags &= ~mask; + } + + public void clearModificationStamp() { + modStamp = IResource.NULL_STAMP; + } + + public synchronized void clearSessionProperties() { + sessionProperties = null; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; // never gets here. + } + } + + public int getCharsetGenerationCount() { + return charsetAndContentId >> 16; + } + + public int getContentId() { + return charsetAndContentId & LOWER; + } + + public FileStoreRoot getFileStoreRoot() { + return fileStoreRoot; + } + + /** + * Returns the set of flags for this info. + */ + public int getFlags() { + return flags; + } + + /** + * Gets the local-relative sync information. + */ + public long getLocalSyncInfo() { + return localInfo; + } + + /** + * Returns the marker generation count. + * The count is incremented whenever markers on the resource change. + */ + public int getMarkerGenerationCount() { + return markerAndSyncStamp >> 16; + } + + /** + * Returns a copy of the collection of makers on this resource. + * null is returned if there are none. + */ + public MarkerSet getMarkers() { + return getMarkers(true); + } + + /** + * Returns the collection of makers on this resource. + * null is returned if there are none. + */ + public MarkerSet getMarkers(boolean makeCopy) { + if (markers == null) + return null; + return makeCopy ? (MarkerSet) markers.clone() : markers; + } + + public long getModificationStamp() { + return modStamp; + } + + public long getNodeId() { + return nodeId; + } + + /** + * Returns the property store associated with this info. The return value may be null. + */ + public Object getPropertyStore() { + return null; + } + + /** + * Returns a copy of the map of this resource session properties. + * An empty map is returned if there are none. + */ + @SuppressWarnings({"unchecked"}) + public Map getSessionProperties() { + // thread safety: (Concurrency001) + ObjectMap temp = sessionProperties; + if (temp == null) + temp = new ObjectMap<>(5); + else + temp = (ObjectMap) sessionProperties.clone(); + return temp; + } + + /** + * Returns the value of the identified session property + */ + public Object getSessionProperty(QualifiedName name) { + // thread safety: (Concurrency001) + Map temp = sessionProperties; + if (temp == null) + return null; + return temp.get(name); + } + + /** + * The parameter to this method is the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + @SuppressWarnings({"unchecked"}) + public synchronized ObjectMap getSyncInfo(boolean makeCopy) { + if (syncInfo == null) + return null; + return makeCopy ? (ObjectMap) syncInfo.clone() : syncInfo; + } + + public synchronized byte[] getSyncInfo(QualifiedName id, boolean makeCopy) { + // thread safety: (Concurrency001) + byte[] b; + if (syncInfo == null) + return null; + b = (byte[]) syncInfo.get(id); + return b == null ? null : (makeCopy ? (byte[]) b.clone() : b); + } + + /** + * Returns the sync information generation count. + * The count is incremented whenever sync info on the resource changes. + */ + public int getSyncInfoGenerationCount() { + return markerAndSyncStamp & LOWER; + } + + /** + * Returns the type setting for this info. Valid values are + * FILE, FOLDER, PROJECT, + */ + public int getType() { + return getType(flags); + } + + /** + * Increments the charset generation count. + * The count is incremented whenever the encoding on the resource changes. + */ + public void incrementCharsetGenerationCount() { + //increment high order bits + charsetAndContentId = ((charsetAndContentId + LOWER + 1) & UPPER) + (charsetAndContentId & LOWER); + } + + /** + * Mark this resource info as having changed content + */ + public void incrementContentId() { + //increment low order bits + charsetAndContentId = (charsetAndContentId & UPPER) + ((charsetAndContentId + 1) & LOWER); + } + + /** + * Increments the marker generation count. + * The count is incremented whenever markers on the resource change. + */ + public void incrementMarkerGenerationCount() { + //increment high order bits + markerAndSyncStamp = ((markerAndSyncStamp + LOWER + 1) & UPPER) + (markerAndSyncStamp & LOWER); + } + + /** + * Change the modification stamp to indicate that this resource has changed. + * The exact value of the stamp doesn't matter, as long as it can be used to + * distinguish two arbitrary resource generations. + */ + public void incrementModificationStamp() { + modStamp++; + } + + /** + * Increments the sync information generation count. + * The count is incremented whenever sync info on the resource changes. + */ + public void incrementSyncInfoGenerationCount() { + //increment low order bits + markerAndSyncStamp = (markerAndSyncStamp & UPPER) + ((markerAndSyncStamp + 1) & LOWER); + } + + /** + * Returns true if all of the bits indicated by the mask are set. + */ + public boolean isSet(int mask) { + return (flags & mask) == mask; + } + + public void readFrom(int newFlags, DataInput input) throws IOException { + // The flags for this info are read by the visitor (flattener). + // See Workspace.readElement(). This allows the reader to look ahead + // and see what type of info is being loaded. + this.flags = newFlags; + localInfo = input.readLong(); + nodeId = input.readLong(); + charsetAndContentId = input.readInt() & LOWER; + modStamp = input.readLong(); + } + + /** + * Sets all of the bits indicated by the mask. + */ + public void set(int mask) { + flags |= mask; + } + + /** + * Sets the value of the indicated bits to be the given value. + */ + protected void setBits(int mask, int start, int value) { + int baseMask = mask >> start; + int newValue = (value & baseMask) << start; + // thread safety: (guarantee atomic assignment) + int temp = flags; + temp &= ~mask; + temp |= newValue; + flags = temp; + } + + public void setFileStoreRoot(FileStoreRoot fileStoreRoot) { + this.fileStoreRoot = fileStoreRoot; + } + + /** + * Sets the flags for this info. + */ + protected void setFlags(int value) { + flags = value; + } + + /** + * Sets the local-relative sync information. + */ + public void setLocalSyncInfo(long info) { + localInfo = info; + } + + /** + * Sets the collection of makers for this resource. + * null is passed in if there are no markers. + */ + public void setMarkers(MarkerSet value) { + markers = value; + } + + /** + * Sets the resource modification stamp. + */ + public void setModificationStamp(long value) { + this.modStamp = value; + } + + /** + * + */ + public void setNodeId(long id) { + nodeId = id; + // Resource modification stamp starts from current nodeId + // so future generations are distinguishable (bug 160728) + if (modStamp == 0) + modStamp = nodeId; + } + + /** + * Sets the property store associated with this info. The value may be null. + */ + public void setPropertyStore(Object value) { + // needs to be implemented on subclasses + } + + /** + * Sets the identified session property to the given value. If + * the value is null, the property is removed. + */ + @SuppressWarnings({"unchecked"}) + public synchronized void setSessionProperty(QualifiedName name, Object value) { + // thread safety: (Concurrency001) + if (value == null) { + if (sessionProperties == null) + return; + ObjectMap temp = (ObjectMap) sessionProperties.clone(); + temp.remove(name); + if (temp.isEmpty()) + sessionProperties = null; + else + sessionProperties = temp; + } else { + ObjectMap temp = sessionProperties; + if (temp == null) + temp = new ObjectMap<>(5); + else + temp = (ObjectMap) sessionProperties.clone(); + temp.put(name, value); + sessionProperties = temp; + } + } + + /** + * The parameter to this method is the implementing class rather than the + * interface so we ensure that we get it right since we are making certain + * assumptions about the object type w.r.t. casting. + */ + protected void setSyncInfo(ObjectMap syncInfo) { + this.syncInfo = syncInfo; + } + + public synchronized void setSyncInfo(QualifiedName id, byte[] value) { + if (value == null) { + //delete sync info + if (syncInfo == null) + return; + syncInfo.remove(id); + if (syncInfo.isEmpty()) + syncInfo = null; + } else { + //add sync info + if (syncInfo == null) + syncInfo = new ObjectMap<>(5); + syncInfo.put(id, value.clone()); + } + } + + /** + * Sets the type for this info to the given value. Valid values are + * FILE, FOLDER, PROJECT + */ + public void setType(int value) { + setBits(M_TYPE, M_TYPE_START, value); + } + + /* (non-Javadoc + * Method declared on IStringPoolParticipant + */ + @Override + public void shareStrings(StringPool set) { + ObjectMap map = syncInfo; + if (map != null) + map.shareStrings(set); + map = sessionProperties; + if (map != null) + map.shareStrings(set); + MarkerSet markerSet = markers; + if (markerSet != null) + markerSet.shareStrings(set); + } + + public void writeTo(DataOutput output) throws IOException { + // The flags for this info are written by the visitor (flattener). + // See SaveManager.writeElement(). This allows the reader to look ahead + // and see what type of info is being loaded. + output.writeLong(localInfo); + output.writeLong(nodeId); + output.writeInt(getContentId()); + output.writeLong(modStamp); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java new file mode 100644 index 0000000000..6c1ba9a456 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.internal.watson.IPathRequestor; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.QualifiedName; + +/** + * Implements a resource proxy given a path requestor and the resource + * info of the resource currently being visited. + */ +public class ResourceProxy implements IResourceProxy, ICoreConstants { + protected final Workspace workspace = (Workspace) ResourcesPlugin.getWorkspace(); + protected IPathRequestor requestor; + protected ResourceInfo info; + + //cached info + protected IPath fullPath; + protected IResource resource; + + /** + * @see org.eclipse.core.resources.IResourceProxy#getModificationStamp() + */ + @Override + public long getModificationStamp() { + return info.getModificationStamp(); + } + + @Override + public String getName() { + return requestor.requestName(); + } + + @Override + public Object getSessionProperty(QualifiedName key) { + return info.getSessionProperty(key); + } + + @Override + public int getType() { + return info.getType(); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isAccessible() + */ + @Override + public boolean isAccessible() { + int flags = info.getFlags(); + if (info.getType() == IResource.PROJECT) + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_OPEN); + return flags != NULL_FLAG; + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isDerived() + */ + @Override + public boolean isDerived() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_DERIVED); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isLinked() + */ + @Override + public boolean isLinked() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_LINK); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isPhantom() + */ + @Override + public boolean isPhantom() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_PHANTOM); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isTeamPrivateMember() + */ + @Override + public boolean isTeamPrivateMember() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_TEAM_PRIVATE_MEMBER); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#isHidden() + */ + @Override + public boolean isHidden() { + int flags = info.getFlags(); + return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_HIDDEN); + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#requestFullPath() + */ + @Override + public IPath requestFullPath() { + if (fullPath == null) + fullPath = requestor.requestPath(); + return fullPath; + } + + /** + * @see org.eclipse.core.resources.IResourceProxy#requestResource() + */ + @Override + public IResource requestResource() { + if (resource == null) + resource = workspace.newResource(requestFullPath(), info.getType()); + return resource; + } + + protected void reset() { + fullPath = null; + resource = null; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java new file mode 100644 index 0000000000..61ffd98f30 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; + +/** + * + */ +public class ResourceStatus extends Status implements IResourceStatus { + IPath path; + + public ResourceStatus(int type, int code, IPath path, String message, Throwable exception) { + super(type, ResourcesPlugin.PI_RESOURCES, code, message, exception); + this.path = path; + } + + public ResourceStatus(int code, String message) { + this(getSeverity(code), code, null, message, null); + } + + public ResourceStatus(int code, IPath path, String message) { + this(getSeverity(code), code, path, message, null); + } + + public ResourceStatus(int code, IPath path, String message, Throwable exception) { + this(getSeverity(code), code, path, message, exception); + } + + /** + * @see IResourceStatus#getPath() + */ + @Override + public IPath getPath() { + return path; + } + + protected static int getSeverity(int code) { + return code == 0 ? 0 : 1 << (code % 100 / 33); + } + + // for debug only + private String getTypeName() { + switch (getSeverity()) { + case IStatus.OK : + return "OK"; //$NON-NLS-1$ + case IStatus.ERROR : + return "ERROR"; //$NON-NLS-1$ + case IStatus.INFO : + return "INFO"; //$NON-NLS-1$ + case IStatus.WARNING : + return "WARNING"; //$NON-NLS-1$ + default : + return String.valueOf(getSeverity()); + } + } + + // for debug only + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[type: "); //$NON-NLS-1$ + sb.append(getTypeName()); + sb.append("], [path: "); //$NON-NLS-1$ + sb.append(getPath()); + sb.append("], [message: "); //$NON-NLS-1$ + sb.append(getMessage()); + sb.append("], [plugin: "); //$NON-NLS-1$ + sb.append(getPlugin()); + sb.append("], [exception: "); //$NON-NLS-1$ + sb.append(getException()); + sb.append("]\n"); //$NON-NLS-1$ + return sb.toString(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java new file mode 100644 index 0000000000..bba0106b19 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java @@ -0,0 +1,1165 @@ +/******************************************************************************* + * 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] Project Path Variable Support + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.net.URI; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.localstore.FileSystemResourceManager; +import org.eclipse.core.internal.properties.IPropertyManager; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.IResourceTree; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ILock; +import org.eclipse.osgi.util.NLS; + +/** + * @since 2.0 + * + * Implementation note: Since the move/delete hook involves running third + * party code, the workspace lock is not held. This means the workspace + * lock must be re-acquired whenever we need to manipulate the workspace + * in any way. All entry points from third party code back into the tree must + * be done in an acquire/release pair. + */ +class ResourceTree implements IResourceTree { + private boolean isValid = true; + private final FileSystemResourceManager localManager; + /** + * The lock to acquire when the workspace needs to be manipulated + */ + private ILock lock; + private MultiStatus multistatus; + private int updateFlags; + + /** + * Constructor for this class. + */ + public ResourceTree(FileSystemResourceManager localManager, ILock lock, MultiStatus status, int updateFlags) { + super(); + this.localManager = localManager; + this.lock = lock; + this.multistatus = status; + this.updateFlags = updateFlags; + } + + /** + * @see IResourceTree#addToLocalHistory(IFile) + */ + @Override + public void addToLocalHistory(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + if (!file.exists()) + return; + IFileStore store = localManager.getStore(file); + final IFileInfo fileInfo = store.fetchInfo(); + if (!fileInfo.exists()) + return; + localManager.getHistoryStore().addState(file.getFullPath(), store, fileInfo, false); + } finally { + lock.release(); + } + } + + private IFileStore computeDestinationStore(IProjectDescription destDescription) throws CoreException { + URI destLocation = destDescription.getLocationURI(); + // Use the default area if necessary for the destination. + if (destLocation == null) { + IPath rootLocation = ResourcesPlugin.getWorkspace().getRoot().getLocation(); + destLocation = rootLocation.append(destDescription.getName()).toFile().toURI(); + } + return EFS.getStore(destLocation); + } + + /** + * @see IResourceTree#computeTimestamp(IFile) + */ + @Override + public long computeTimestamp(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + if (!file.getProject().exists()) + return NULL_TIMESTAMP; + return internalComputeTimestamp(file); + } finally { + lock.release(); + } + } + + /** + * Copies the local history of source to destination. Note that if source + * is an IFolder, it is assumed that the same structure exists under destination + * and the local history of any IFile under source will be copied to the + * associated IFile under destination. + */ + private void copyLocalHistory(IResource source, IResource destination) { + localManager.getHistoryStore().copyHistory(source, destination, true); + } + + /** + * @see IResourceTree#deletedFile(IFile) + */ + @Override + public void deletedFile(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!file.exists()) + return; + try { + // Delete properties, generate marker deltas, and remove the node from the workspace tree. + ((Resource) file).deleteResource(true, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorDeleting, file.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, file.getFullPath(), message, e); + failed(status); + } + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#deletedFolder(IFolder) + */ + @Override + public void deletedFolder(IFolder folder) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!folder.exists()) + return; + try { + // Delete properties, generate marker deltas, and remove the node from the workspace tree. + ((Resource) folder).deleteResource(true, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorDeleting, folder.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, folder.getFullPath(), message, e); + failed(status); + } + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#deletedProject(IProject) + */ + @Override + public void deletedProject(IProject target) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!target.exists()) + return; + // Delete properties, generate marker deltas, and remove the node from the workspace tree. + try { + ((Project) target).deleteResource(false, null); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorDeleting, target.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, target.getFullPath(), message, e); + // log the status but don't return until we try and delete the rest of the project info + failed(status); + } + } finally { + lock.release(); + } + } + + /** + * Makes sure that the destination directory for a project move is unoccupied. + * Returns true if successful, and false if the move should be aborted + */ + private boolean ensureDestinationEmpty(IProject source, IFileStore destinationStore, IProgressMonitor monitor) throws CoreException { + String message; + //Make sure the destination location is unoccupied + if (!destinationStore.fetchInfo().exists()) + return true; + //check for existing children + if (destinationStore.childNames(EFS.NONE, Policy.subMonitorFor(monitor, 0)).length > 0) { + //allow case rename to proceed + if (((Resource) source).getStore().equals(destinationStore)) + return true; + //fail because the destination is occupied + message = NLS.bind(Messages.localstore_resourceExists, destinationStore); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, null); + failed(status); + return false; + } + //delete the destination directory to allow for efficient renaming + destinationStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, 0)); + return true; + } + + /** + * This operation has failed for the given reason. Add it to this + * resource tree's status. + */ + @Override + public void failed(IStatus reason) { + Assert.isLegal(isValid); + multistatus.add(reason); + } + + /** + * Returns the status object held onto by this resource tree. + */ + protected IStatus getStatus() { + return multistatus; + } + + /** + * @see IResourceTree#getTimestamp(IFile) + */ + @Override + public long getTimestamp(IFile file) { + Assert.isLegal(isValid); + try { + lock.acquire(); + if (!file.exists()) + return NULL_TIMESTAMP; + ResourceInfo info = ((File) file).getResourceInfo(false, false); + return info == null ? NULL_TIMESTAMP : info.getLocalSyncInfo(); + } finally { + lock.release(); + } + } + + /** + * Returns the local timestamp for a file. + * + * @param file + * @return The local file system timestamp + */ + private long internalComputeTimestamp(IFile file) { + IFileInfo fileInfo = localManager.getStore(file).fetchInfo(); + return fileInfo.exists() ? fileInfo.getLastModified() : NULL_TIMESTAMP; + } + + /** + * Helper method for #standardDeleteFile. Returns a boolean indicating whether or + * not the delete was successful. + */ + private boolean internalDeleteFile(IFile file, int flags, IProgressMonitor monitor) { + try { + String message = NLS.bind(Messages.resources_deleting, file.getFullPath()); + monitor.beginTask(message, Policy.totalWork); + Policy.checkCanceled(monitor); + + // Do nothing if the file doesn't exist in the workspace. + if (!file.exists()) { + // Indicate that the delete was successful. + return true; + } + // Don't delete contents if this is a linked resource + if (file.isLinked()) { + deletedFile(file); + return true; + } + // If the file doesn't exist on disk then signal to the workspace to delete the + // file and return. + IFileStore fileStore = localManager.getStore(file); + boolean localExists = fileStore.fetchInfo().exists(); + if (!localExists) { + deletedFile(file); + // Indicate that the delete was successful. + return true; + } + + boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0; + boolean force = (flags & IResource.FORCE) != 0; + + // Add the file to the local history if requested by the user. + if (keepHistory) + addToLocalHistory(file); + monitor.worked(Policy.totalWork / 4); + + // We want to fail if force is false and the file is not synchronized with the + // local file system. + if (!force) { + boolean inSync = isSynchronized(file, IResource.DEPTH_ZERO); + // only want to fail if the file still exists. + if (!inSync && localExists) { + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, file.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, file.getFullPath(), message); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + } + monitor.worked(Policy.totalWork / 4); + + // Try to delete the file from the file system. + try { + fileStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork / 4)); + // If the file was successfully deleted from the file system the + // workspace tree should be updated accordingly. + deletedFile(file); + // Indicate that the delete was successful. + return true; + } catch (CoreException e) { + message = NLS.bind(Messages.resources_couldnotDelete, fileStore.toString()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message, e); + failed(status); + } + // Indicate that the delete was unsuccessful. + return false; + } finally { + monitor.done(); + } + } + + /** + * Helper method for #standardDeleteFolder. Returns a boolean indicating + * whether or not the deletion of this folder was successful. Does a best effort + * delete of this resource and its children. + */ + private boolean internalDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) { + String message = NLS.bind(Messages.resources_deleting, folder.getFullPath()); + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + monitor.subTask(message); + Policy.checkCanceled(monitor); + + // Do nothing if the folder doesn't exist in the workspace. + if (!folder.exists()) + return true; + + // Don't delete contents if this is a linked resource + if (folder.isLinked()) { + deletedFolder(folder); + return true; + } + + // If the folder doesn't exist on disk then update the tree and return. + IFileStore fileStore = localManager.getStore(folder); + if (!fileStore.fetchInfo().exists()) { + deletedFolder(folder); + return true; + } + + try { + //this will delete local and workspace + localManager.delete(folder, flags, Policy.subMonitorFor(monitor, Policy.totalWork)); + } catch (CoreException ce) { + message = NLS.bind(Messages.localstore_couldnotDelete, folder.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, folder.getFullPath(), message, ce); + failed(status); + return false; + } + return true; + } + + /** + * Does a best-effort delete on this resource and all its children. + */ + private boolean internalDeleteProject(IProject project, int flags, IProgressMonitor monitor) { + // Recursively delete each member of the project. + IResource[] members = null; + try { + members = project.members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMembers, project.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, project.getFullPath(), message, e); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + boolean deletedChildren = true; + for (int i = 0; i < members.length; i++) { + IResource child = members[i]; + switch (child.getType()) { + case IResource.FILE : + // ignore the .project file for now and delete it last + if (!IProjectDescription.DESCRIPTION_FILE_NAME.equals(child.getName())) + deletedChildren &= internalDeleteFile((IFile) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length)); + break; + case IResource.FOLDER : + deletedChildren &= internalDeleteFolder((IFolder) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length)); + break; + } + } + IFileStore projectStore = localManager.getStore(project); + // Check to see if the children were deleted ok. If there was a problem + // just return as the problem should have been logged by the recursive + // call to the child. + if (!deletedChildren) + // Indicate that the delete was unsuccessful. + return false; + + //Check if there are any undiscovered children of the project on disk other than description file + String[] children; + try { + children = projectStore.childNames(EFS.NONE, null); + } catch (CoreException e) { + //treat failure to access the directory as a non-existent directory + children = new String[0]; + } + boolean force = BitMask.isSet(flags, IResource.FORCE); + if (!force && (children.length != 1 || !IProjectDescription.DESCRIPTION_FILE_NAME.equals(children[0]))) { + String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, project.getName()); + failed(new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, project.getFullPath(), message)); + return false; + } + + //Now delete the project description file + IResource file = project.findMember(IProjectDescription.DESCRIPTION_FILE_NAME); + if (file == null) { + //the .project have may have been recreated on disk automatically by snapshot + IFileStore dotProject = projectStore.getChild(IProjectDescription.DESCRIPTION_FILE_NAME); + try { + dotProject.delete(EFS.NONE, null); + } catch (CoreException e) { + failed(e.getStatus()); + } + } else { + boolean deletedProjectFile = internalDeleteFile((IFile) file, flags, Policy.monitorFor(null)); + if (!deletedProjectFile) { + String message = NLS.bind(Messages.resources_couldnotDelete, file.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + } + + //children are deleted, so now delete the parent + try { + projectStore.delete(EFS.NONE, null); + deletedProject(project); + // Indicate that the delete was successful. + return true; + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_couldnotDelete, projectStore.toString()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message, e); + failed(status); + // Indicate that the delete was unsuccessful. + return false; + } + } + + /** + * Return true if there is a change in the content area for the project. + */ + private boolean isContentChange(IProject project, IProjectDescription destDescription) { + IProjectDescription srcDescription = ((Project) project).internalGetDescription(); + URI srcLocation = srcDescription.getLocationURI(); + URI destLocation = destDescription.getLocationURI(); + if (srcLocation == null || destLocation == null) + return true; + //don't use URIUtil because we want to treat case rename as a content change + return !srcLocation.equals(destLocation); + } + + /** + * Return true if there is a change in the name of the project. + */ + private boolean isNameChange(IProject project, IProjectDescription description) { + return !project.getName().equals(description.getName()); + } + + /** + * Refreshes the resource hierarchy with its children. In case of failure + * adds an appropriate status to the resource tree's status. + */ + private void safeRefresh(IResource resource) { + try { + resource.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); + } catch (CoreException ce) { + IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, resource.getFullPath(), Messages.refresh_refreshErr, ce); + failed(status); + } + } + + /** + * @see IResourceTree#isSynchronized(IResource, int) + */ + @Override + public boolean isSynchronized(IResource resource, int depth) { + try { + lock.acquire(); + return localManager.isSynchronized(resource, depth); + } finally { + lock.release(); + } + } + + /** + * The specific operation for which this tree was created has completed and this tree + * should not be used anymore. Ensure that this is the case by making it invalid. This + * is checked by all API methods. + */ + void makeInvalid() { + this.isValid = false; + } + + /** + * @see IResourceTree#movedFile(IFile, IFile) + */ + @Override + public void movedFile(IFile source, IFile destination) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the resource doesn't exist. + if (!source.exists()) + return; + // If the destination already exists then we have a problem. + if (destination.exists()) { + String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); + // log the status but don't return until we try and move the rest of the resource information. + failed(status); + } + + // Move the resource's persistent properties. + IPropertyManager propertyManager = ((Resource) source).getPropertyManager(); + try { + propertyManager.copy(source, destination, IResource.DEPTH_ZERO); + propertyManager.deleteProperties(source, IResource.DEPTH_ZERO); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource information. + failed(status); + } + + // Move the node in the workspace tree. + Workspace workspace = (Workspace) source.getWorkspace(); + try { + workspace.move((Resource) source, destination.getFullPath(), IResource.DEPTH_ZERO, updateFlags, false); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource information. + failed(status); + } + + // Generate the marker deltas. + try { + workspace.getMarkerManager().moved(source, destination, IResource.DEPTH_ZERO); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + } + + // Copy the local history information + copyLocalHistory(source, destination); + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#movedFolderSubtree(IFolder, IFolder) + */ + @Override + public void movedFolderSubtree(IFolder source, IFolder destination) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the source resource doesn't exist. + if (!source.exists()) + return; + // If the destination already exists then we have an error. + if (destination.exists()) { + String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); + failed(status); + return; + } + + // Move the folder properties. + int depth = IResource.DEPTH_INFINITE; + IPropertyManager propertyManager = ((Resource) source).getPropertyManager(); + try { + propertyManager.copy(source, destination, depth); + propertyManager.deleteProperties(source, depth); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Create the destination node in the tree. + Workspace workspace = (Workspace) source.getWorkspace(); + try { + workspace.move((Resource) source, destination.getFullPath(), depth, updateFlags, false); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Generate the marker deltas. + try { + workspace.getMarkerManager().moved(source, destination, depth); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + } + + // Copy the local history for this folder + copyLocalHistory(source, destination); + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#movedProjectSubtree(IProject, IProjectDescription) + */ + @Override + public boolean movedProjectSubtree(IProject project, IProjectDescription destDescription) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the source resource doesn't exist. + if (!project.exists()) + return true; + + Project source = (Project) project; + Project destination = (Project) source.getWorkspace().getRoot().getProject(destDescription.getName()); + Workspace workspace = (Workspace) source.getWorkspace(); + int depth = IResource.DEPTH_INFINITE; + + // If the name of the source and destination projects are not the same then + // rename the meta area and make changes in the tree. + if (isNameChange(source, destDescription)) { + if (destination.exists()) { + String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); + failed(status); + return false; + } + + // Rename the project metadata area. Close the property store to flush everything to disk + try { + source.getPropertyManager().closePropertyStore(source); + localManager.getHistoryStore().closeHistoryStore(source); + } catch (CoreException e) { + String message = NLS.bind(Messages.properties_couldNotClose, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + final IFileSystem fileSystem = EFS.getLocalFileSystem(); + IFileStore oldMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(source)); + IFileStore newMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(destination)); + try { + oldMetaArea.move(newMetaArea, EFS.NONE, new NullProgressMonitor()); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_moveMeta, oldMetaArea, newMetaArea); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_WRITE_METADATA, destination.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Move the workspace tree. + try { + workspace.move(source, destination.getFullPath(), depth, updateFlags, true); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + + // Clear stale state on the destination project. + ((ProjectInfo) destination.getResourceInfo(false, true)).fixupAfterMove(); + + // Generate marker deltas. + try { + workspace.getMarkerManager().moved(source, destination, depth); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorMarkersMove, source.getFullPath(), destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + // log the status but don't return until we try and move the rest of the resource info + failed(status); + } + // Copy the local history + copyLocalHistory(source, destination); + } + + // Write the new project description on the destination project. + try { + //moving linked resources may have modified the description in memory + ((ProjectDescription) destDescription).setLinkDescriptions(destination.internalGetDescription().getLinks()); + // moving filters may have modified the description in memory + ((ProjectDescription) destDescription).setFilterDescriptions(destination.internalGetDescription().getFilters()); + // moving variables may have modified the description in memory + ((ProjectDescription) destDescription).setVariableDescriptions(destination.internalGetDescription().getVariables()); + destination.internalSetDescription(destDescription, true); + destination.writeDescription(IResource.FORCE); + } catch (CoreException e) { + String message = Messages.resources_projectDesc; + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e); + failed(status); + } + + // write the private project description, including the project location + try { + workspace.getMetaArea().writePrivateDescription(destination); + } catch (CoreException e) { + failed(e.getStatus()); + } + + // Do a refresh on the destination project to pick up any newly discovered resources + try { + destination.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_errorRefresh, destination.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e); + failed(status); + return false; + } + return true; + } finally { + lock.release(); + } + } + + /** + * Helper method for moving the project content. Determines the content location + * based on the project description. (default location or user defined?) + */ + private void moveProjectContent(IProject source, IFileStore destStore, int flags, IProgressMonitor monitor) throws CoreException { + try { + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.beginTask(message, 10); + IProjectDescription srcDescription = source.getDescription(); + URI srcLocation = srcDescription.getLocationURI(); + // If the locations are the same (and non-default) then there is nothing to do. + if (srcLocation != null && URIUtil.equals(srcLocation, destStore.toURI())) + return; + + //If this is a replace, just make sure the destination location exists, and return + boolean replace = (flags & IResource.REPLACE) != 0; + if (replace) { + destStore.mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 10)); + return; + } + + // Move the contents on disk. + localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 9)); + + //if this is a deep move, move the contents of any linked resources + if ((flags & IResource.SHALLOW) == 0) { + IResource[] children = source.members(); + for (int i = 0; i < children.length; i++) { + if (children[i].isLinked()) { + message = NLS.bind(Messages.resources_moving, children[i].getFullPath()); + monitor.subTask(message); + IFileStore linkDestination = destStore.getChild(children[i].getName()); + try { + localManager.move(children[i], linkDestination, flags, Policy.monitorFor(null)); + } catch (CoreException ce) { + //log the failure, but keep trying on remaining links + failed(ce.getStatus()); + } + } + } + } + monitor.worked(1); + } finally { + monitor.done(); + } + } + + /** + * @see IResourceTree#standardDeleteFile(IFile, int, IProgressMonitor) + */ + @Override + public void standardDeleteFile(IFile file, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + internalDeleteFile(file, flags, monitor); + } finally { + lock.release(); + } + } + + /** + * @see IResourceTree#standardDeleteFolder(IFolder, int, IProgressMonitor) + */ + @Override + public void standardDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + internalDeleteFolder(folder, flags, monitor); + } catch (OperationCanceledException oce) { + safeRefresh(folder); + throw oce; + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardDeleteProject(IProject, int, IProgressMonitor) + */ + @Override + public void standardDeleteProject(IProject project, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_deleting, project.getFullPath()); + monitor.beginTask(message, Policy.totalWork); + // Do nothing if the project doesn't exist in the workspace tree. + if (!project.exists()) + return; + + boolean alwaysDeleteContent = (flags & IResource.ALWAYS_DELETE_PROJECT_CONTENT) != 0; + boolean neverDeleteContent = (flags & IResource.NEVER_DELETE_PROJECT_CONTENT) != 0; + boolean success = true; + + // Delete project content. Don't do anything if the user specified explicitly asked + // not to delete the project content or if the project is closed and + // ALWAYS_DELETE_PROJECT_CONTENT was not specified. + if (alwaysDeleteContent || (project.isOpen() && !neverDeleteContent)) { + // Force is implied if alwaysDeleteContent is true or if the project is in sync + // with the local file system. + if (alwaysDeleteContent || isSynchronized(project, IResource.DEPTH_INFINITE)) { + flags |= IResource.FORCE; + } + + // If the project is open we have to recursively try and delete all the files doing best-effort. + if (project.isOpen()) { + success = internalDeleteProject(project, flags, monitor); + if (!success) { + IFileStore store = localManager.getStore(project); + message = NLS.bind(Messages.resources_couldnotDelete, store.toString()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message); + failed(status); + } + return; + } + + // If the project is closed we can short circuit this operation and delete all the files on disk. + // The .project file is deleted at the end of the operation. + try { + IFileStore projectStore = localManager.getStore(project); + IFileStore members[] = projectStore.childStores(EFS.NONE, null); + for (int i = 0; i < members.length; i++) { + if (!IProjectDescription.DESCRIPTION_FILE_NAME.equals(members[i].getName())) + members[i].delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8 / members.length)); + } + projectStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8 / (members.length > 0 ? members.length : 1))); + } catch (OperationCanceledException oce) { + safeRefresh(project); + throw oce; + } catch (CoreException ce) { + message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message, ce); + failed(status); + return; + } + } + + // Signal that the workspace tree should be updated that the project has been deleted. + if (success) + deletedProject(project); + else { + message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message); + failed(status); + } + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardMoveFile(IFile, IFile, int, IProgressMonitor) + */ + @Override + public void standardMoveFile(IFile source, IFile destination, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.subTask(message); + + // These pre-conditions should all be ok but just in case... + if (!source.exists() || destination.exists() || !destination.getParent().isAccessible()) + throw new IllegalArgumentException(); + + boolean force = (flags & IResource.FORCE) != 0; + boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0; + boolean isDeep = (flags & IResource.SHALLOW) == 0; + + // If the file is not in sync with the local file system and force is false, + // then signal that we have an error. + if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message); + failed(status); + return; + } + monitor.worked(Policy.totalWork / 4); + + // Add the file contents to the local history if requested by the user. + if (keepHistory) + addToLocalHistory(source); + monitor.worked(Policy.totalWork / 4); + + //for shallow move of linked resources, nothing needs to be moved in the file system + if (!isDeep && source.isLinked()) { + movedFile(source, destination); + return; + } + + // If the file was successfully moved in the file system then the workspace + // tree needs to be updated accordingly. Otherwise signal that we have an error. + IFileStore destStore = null; + boolean failedDeletingSource = false; + try { + destStore = localManager.getStore(destination); + //ensure parent of destination exists + destStore.getParent().mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 0)); + localManager.move(source, destStore, flags, monitor); + } catch (CoreException e) { + failed(e.getStatus()); + // did the fail occur after copying to the destination? + failedDeletingSource = destStore != null && destStore.fetchInfo().exists(); + // if so, we should proceed + if (!failedDeletingSource) + return; + } + movedFile(source, destination); + updateMovedFileTimestamp(destination, internalComputeTimestamp(destination)); + if (failedDeletingSource) { + //recreate source file to ensure we are not out of sync + try { + source.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e) { + //ignore secondary failure - we have already logged the main failure + } + } + monitor.worked(Policy.totalWork / 4); + return; + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardMoveFolder(IFolder, IFolder, int, IProgressMonitor) + */ + @Override + public void standardMoveFolder(IFolder source, IFolder destination, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.beginTask(message, 100); + + // These pre-conditions should all be ok but just in case... + if (!source.exists() || destination.exists() || !destination.getParent().isAccessible()) + throw new IllegalArgumentException(); + + // Check to see if we are synchronized with the local file system. If we are in sync then we can + // short circuit this method and do a file system only move. Otherwise we have to recursively + // try and move all resources, doing it in a best-effort manner. + boolean force = (flags & IResource.FORCE) != 0; + if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message); + failed(status); + return; + } + monitor.worked(20); + + //for linked resources, nothing needs to be moved in the file system + boolean isDeep = (flags & IResource.SHALLOW) == 0; + if (!isDeep && (source.isLinked() || source.isVirtual())) { + movedFolderSubtree(source, destination); + return; + } + + // Move the resources in the file system. Only the FORCE flag is valid here so don't + // have to worry about clearing the KEEP_HISTORY flag. + IFileStore destStore = null; + boolean failedDeletingSource = false; + try { + destStore = localManager.getStore(destination); + localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 60)); + } catch (CoreException e) { + failed(e.getStatus()); + // did the fail occur after copying to the destination? + failedDeletingSource = destStore != null && destStore.fetchInfo().exists(); + // if so, we should proceed + if (!failedDeletingSource) + return; + } + movedFolderSubtree(source, destination); + monitor.worked(20); + updateTimestamps(destination, isDeep); + if (failedDeletingSource) { + //the move could have been partially successful, so refresh to ensure we are in sync + try { + source.refreshLocal(IResource.DEPTH_INFINITE, null); + destination.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e) { + //ignore secondary failures -we have already logged main failure + } + } + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#standardMoveProject(IProject, IProjectDescription, int, IProgressMonitor) + */ + @Override + public void standardMoveProject(IProject source, IProjectDescription description, int flags, IProgressMonitor monitor) { + Assert.isLegal(isValid); + try { + lock.acquire(); + String message = NLS.bind(Messages.resources_moving, source.getFullPath()); + monitor.beginTask(message, Policy.totalWork); + + // Double-check this pre-condition. + if (!source.isAccessible()) + throw new IllegalArgumentException(); + + // If there is nothing to do on disk then signal to make the workspace tree + // changes. + if (!isContentChange(source, description)) { + movedProjectSubtree(source, description); + return; + } + + // Check to see if we are synchronized with the local file system. + boolean force = (flags & IResource.FORCE) != 0; + if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { + // FIXME: make this a best effort move? + message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); + IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message); + failed(status); + return; + } + + IFileStore destinationStore; + try { + destinationStore = computeDestinationStore(description); + //destination can be non-empty on replace + if ((flags & IResource.REPLACE) == 0) + if (!ensureDestinationEmpty(source, destinationStore, monitor)) + return; + } catch (CoreException e) { + //must fail if the destination location cannot be accessd (undefined file system) + message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + return; + } + + // Move the project content in the local file system. + try { + moveProjectContent(source, destinationStore, flags, Policy.subMonitorFor(monitor, Policy.totalWork * 3 / 4)); + } catch (CoreException e) { + message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath()); + IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); + failed(status); + //refresh the project because it might have been partially moved + try { + source.refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e2) { + //ignore secondary failures + } + } + + // If we got this far the project content has been moved on disk (if necessary) + // and we need to update the workspace tree. + movedProjectSubtree(source, description); + monitor.worked(Policy.totalWork * 1 / 8); + + boolean isDeep = (flags & IResource.SHALLOW) == 0; + updateTimestamps(source.getWorkspace().getRoot().getProject(description.getName()), isDeep); + monitor.worked(Policy.totalWork * 1 / 8); + } finally { + lock.release(); + monitor.done(); + } + } + + /** + * @see IResourceTree#updateMovedFileTimestamp(IFile, long) + */ + @Override + public void updateMovedFileTimestamp(IFile file, long timestamp) { + Assert.isLegal(isValid); + try { + lock.acquire(); + // Do nothing if the file doesn't exist in the workspace tree. + if (!file.exists()) + return; + // Update the timestamp in the tree. + ResourceInfo info = ((Resource) file).getResourceInfo(false, true); + // The info should never be null since we just checked that the resource exists in the tree. + localManager.updateLocalSync(info, timestamp); + //remove the linked bit since this resource has been moved in the file system + info.clear(ICoreConstants.M_LINK); + } finally { + lock.release(); + } + } + + /** + * Helper method to update all the timestamps in the tree to match + * those in the file system. Used after a #move. + */ + private void updateTimestamps(IResource root, final boolean isDeep) { + IResourceVisitor visitor = new IResourceVisitor() { + @Override + public boolean visit(IResource resource) { + if (resource.isLinked()) { + if (isDeep && !((Resource) resource).isUnderVirtual()) { + //clear the linked resource bit, if any + ResourceInfo info = ((Resource) resource).getResourceInfo(false, true); + info.clear(ICoreConstants.M_LINK); + } + return true; + } + //only needed if underlying file system does not preserve timestamps + // if (resource.getType() == IResource.FILE) { + // IFile file = (IFile) resource; + // updateMovedFileTimestamp(file, computeTimestamp(file)); + // } + return true; + } + }; + try { + root.accept(visitor, IResource.DEPTH_INFINITE, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + } catch (CoreException e) { + // No exception should be thrown. + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java new file mode 100644 index 0000000000..8def07a9c0 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * 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.runtime.QualifiedName; + +public class RootInfo extends ResourceInfo { + /** The property store for this resource */ + protected Object propertyStore = null; + + /** + * Returns the property store associated with this info. The return value may be null. + */ + @Override + public Object getPropertyStore() { + return propertyStore; + } + + /** + * Override parent's behaviour and do nothing. Sync information + * cannot be stored on the workspace root so we don't need to + * update this counter which is used for deltas. + */ + @Override + public void incrementSyncInfoGenerationCount() { + // do nothing + } + + /** + * Sets the property store associated with this info. The value may be null. + */ + @Override + public void setPropertyStore(Object value) { + propertyStore = value; + } + + /** + * Overrides parent's behaviour since sync information is not + * stored on the workspace root. + */ + @Override + public void setSyncInfo(QualifiedName id, byte[] value) { + // do nothing + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java new file mode 100644 index 0000000000..b28e19237f --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java @@ -0,0 +1,230 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.internal.events.ILifecycleListener; +import org.eclipse.core.internal.events.LifecycleEvent; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.ResourceRuleFactory; +import org.eclipse.core.resources.team.TeamHook; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; + +/** + * Class for calculating scheduling rules for resource changing operations. + * This factory delegates to the TeamHook to obtain an appropriate factory + * for the resource that the operation is proposing to modify. + */ +class Rules implements IResourceRuleFactory, ILifecycleListener { + private final ResourceRuleFactory defaultFactory = new ResourceRuleFactory() {/**/}; + /** + * Map of project names to the factory for that project. + */ + private final Map projectsToRules = Collections.synchronizedMap(new HashMap()); + private final TeamHook teamHook; + private final IWorkspaceRoot root; + + /** + * Creates a new scheduling rule factory for the given workspace + * @param workspace + */ + Rules(Workspace workspace) { + this.root = workspace.getRoot(); + this.teamHook = workspace.getTeamHook(); + workspace.addLifecycleListener(this); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a build operation. + */ + @Override + public ISchedulingRule buildRule() { + //team hook currently cannot change this rule + return root; + } + + /** + * Obtains the scheduling rule from the appropriate factories for a copy operation. + */ + @Override + public ISchedulingRule copyRule(IResource source, IResource destination) { + if (source.getType() == IResource.ROOT || destination.getType() == IResource.ROOT) + return root; + //source is not modified, destination is created + return factoryFor(destination).copyRule(source, destination); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a create operation. + */ + @Override + public ISchedulingRule createRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).createRule(resource); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a delete operation. + */ + @Override + public ISchedulingRule deleteRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).deleteRule(resource); + } + + /** + * Returns the scheduling rule factory for the given resource + */ + private IResourceRuleFactory factoryFor(IResource destination) { + IResourceRuleFactory fac = projectsToRules.get(destination.getFullPath().segment(0)); + if (fac == null) { + //use the default factory if the project is not yet accessible + if (!destination.getProject().isAccessible()) + return defaultFactory; + //ask the team hook to supply one + fac = teamHook.getRuleFactory(destination.getProject()); + projectsToRules.put(destination.getFullPath().segment(0), fac); + } + return fac; + } + + @Override + public void handleEvent(LifecycleEvent event) { + //clear resource rule factory for projects that are about to be closed + //or deleted. It is ok to do this during a PRE event because the rule + //has already been obtained at this point. + switch (event.kind) { + case LifecycleEvent.PRE_PROJECT_CLOSE : + case LifecycleEvent.PRE_PROJECT_DELETE : + case LifecycleEvent.PRE_PROJECT_MOVE : + setRuleFactory((IProject) event.resource, null); + } + } + + /** + * Obtains the scheduling rule from the appropriate factory for a charset change operation. + */ + @Override + public ISchedulingRule charsetRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return null; + return factoryFor(resource).charsetRule(resource); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a derived flag change operation. + */ + @Override + public ISchedulingRule derivedRule(IResource resource) { + //team hook currently cannot change this rule + return null; + } + + /** + * Obtains the scheduling rule from the appropriate factory for a marker change operation. + */ + @Override + public ISchedulingRule markerRule(IResource resource) { + //team hook currently cannot change this rule + return null; + } + + /** + * Obtains the scheduling rule from the appropriate factory for a sync info change operation. + */ + @Override + public ISchedulingRule syncInfoRule(IResource resource) { + //team hook currently cannot change this rule + return null; + } + + /** + * Obtains the scheduling rule from the appropriate factory for a modify operation. + */ + @Override + public ISchedulingRule modifyRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).modifyRule(resource); + } + + /** + * Obtains the scheduling rule from the appropriate factories for a move operation. + */ + @Override + public ISchedulingRule moveRule(IResource source, IResource destination) { + if (source.getType() == IResource.ROOT || destination.getType() == IResource.ROOT) + return root; + //treat a move across projects as a create on the destination and a delete on the source + if (!source.getFullPath().segment(0).equals(destination.getFullPath().segment(0))) + return MultiRule.combine(modifyRule(source.getProject()), modifyRule(destination.getProject())); + return factoryFor(source).moveRule(source, destination); + } + + /** + * Obtains the scheduling rule from the appropriate factory for a refresh operation. + */ + @Override + public ISchedulingRule refreshRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return root; + return factoryFor(resource).refreshRule(resource); + } + + /* (non-javadoc) + * Implements TeamHook#setRuleFactory + */ + void setRuleFactory(IProject project, IResourceRuleFactory factory) { + if (factory == null) + projectsToRules.remove(project.getName()); + else + projectsToRules.put(project.getName(), factory); + } + + /** + * Combines rules for each parameter to validateEdit from the corresponding + * rule factories. + */ + @Override + public ISchedulingRule validateEditRule(IResource[] resources) { + if (resources.length == 0) + return null; + //optimize rule for single file + if (resources.length == 1) { + if (resources[0].getType() == IResource.ROOT) + return root; + return factoryFor(resources[0]).validateEditRule(resources); + } + //gather rules for each resource from appropriate factory + HashSet rules = new HashSet<>(); + IResource[] oneResource = new IResource[1]; + for (int i = 0; i < resources.length; i++) { + if (resources[i].getType() == IResource.ROOT) + return root; + oneResource[0] = resources[i]; + ISchedulingRule rule = factoryFor(resources[i]).validateEditRule(oneResource); + if (rule != null) + rules.add(rule); + } + if (rules.isEmpty()) + return null; + if (rules.size() == 1) + return rules.iterator().next(); + ISchedulingRule[] ruleArray = rules.toArray(new ISchedulingRule[rules.size()]); + return new MultiRule(ruleArray); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java new file mode 100644 index 0000000000..35e67306e6 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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.resources; + +import java.io.*; +import java.util.Properties; +import java.util.Set; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; + +/** + * Represents a table of keys and paths used by a plugin to maintain its + * configuration files' names. + */ +public class SafeFileTable { + protected IPath location; + protected Properties table; + + public SafeFileTable(String pluginId) throws CoreException { + location = getWorkspace().getMetaArea().getSafeTableLocationFor(pluginId); + restore(); + } + + public IPath[] getFiles() { + Set set = table.keySet(); + String[] keys = set.toArray(new String[set.size()]); + IPath[] files = new IPath[keys.length]; + for (int i = 0; i < keys.length; i++) + files[i] = new Path(keys[i]); + return files; + } + + protected Workspace getWorkspace() { + return (Workspace) ResourcesPlugin.getWorkspace(); + } + + public IPath lookup(IPath file) { + String result = table.getProperty(file.toOSString()); + return result == null ? null : new Path(result); + } + + public void map(IPath file, IPath aLocation) { + if (aLocation == null) + table.remove(file); + else + table.setProperty(file.toOSString(), aLocation.toOSString()); + } + + public void restore() throws CoreException { + java.io.File target = location.toFile(); + table = new Properties(); + if (!target.exists()) + return; + try { + FileInputStream input = new FileInputStream(target); + try { + table.load(input); + } finally { + input.close(); + } + } catch (IOException e) { + String message = Messages.resources_exSafeRead; + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); + } + } + + public void save() throws CoreException { + java.io.File target = location.toFile(); + try { + FileOutputStream output = new FileOutputStream(target); + try { + table.store(output, "safe table"); //$NON-NLS-1$ + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (IOException e) { + String message = Messages.resources_exSafeSave; + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); + } + } + + public void setLocation(IPath location) { + if (location != null) + this.location = location; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java new file mode 100644 index 0000000000..2185abba04 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; + +public class SaveContext implements ISaveContext { + protected String pluginId; + protected int kind; + protected boolean needDelta; + protected boolean needSaveNumber; + protected SafeFileTable fileTable; + protected int previousSaveNumber; + protected IProject project; + + protected SaveContext(String pluginId, int kind, IProject project) throws CoreException { + this.kind = kind; + this.project = project; + this.pluginId = pluginId; + needDelta = false; + needSaveNumber = false; + fileTable = new SafeFileTable(pluginId); + previousSaveNumber = getWorkspace().getSaveManager().getSaveNumber(pluginId); + } + + public void commit() throws CoreException { + if (needSaveNumber) { + IPath oldLocation = getWorkspace().getMetaArea().getSafeTableLocationFor(pluginId); + getWorkspace().getSaveManager().setSaveNumber(pluginId, getSaveNumber()); + fileTable.setLocation(getWorkspace().getMetaArea().getSafeTableLocationFor(pluginId)); + fileTable.save(); + oldLocation.toFile().delete(); + } + } + + /** + * @see ISaveContext + */ + @Override + public IPath[] getFiles() { + return getFileTable().getFiles(); + } + + protected SafeFileTable getFileTable() { + return fileTable; + } + + /** + * @see ISaveContext + */ + @Override + public int getKind() { + return kind; + } + + public String getPluginId() { + return pluginId; + } + + /** + * @see ISaveContext + */ + @Override + public int getPreviousSaveNumber() { + return previousSaveNumber; + } + + /** + * @see ISaveContext + */ + @Override + public IProject getProject() { + return project; + } + + /** + * @see ISaveContext + */ + @Override + public int getSaveNumber() { + int result = getPreviousSaveNumber() + 1; + return result > 0 ? result : 1; + } + + protected Workspace getWorkspace() { + return (Workspace) ResourcesPlugin.getWorkspace(); + } + + public boolean isDeltaNeeded() { + return needDelta; + } + + /** + * @see ISaveContext + */ + @Override + public IPath lookup(IPath file) { + return getFileTable().lookup(file); + } + + /** + * @see ISaveContext + */ + @Override + public void map(IPath file, IPath location) { + getFileTable().map(file, location); + } + + /** + * @see ISaveContext + */ + @Override + public void needDelta() { + needDelta = true; + } + + /** + * @see ISaveContext + */ + @Override + public void needSaveNumber() { + needSaveNumber = true; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java new file mode 100644 index 0000000000..f611fbda3c --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java @@ -0,0 +1,2139 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Francis Lynch (Wind River) - [301563] Save and load tree snapshots + * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project + * Baltasar Belyavsky (Texas Instruments) - [361675] Order mismatch when saving/restoring workspace trees + * Broadcom Corporation - ongoing development + * Sergey Prigogin (Google) - [437005] Out-of-date .snap file prevents Eclipse from running + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.io.File; +import java.net.URI; +import java.util.*; +import java.util.zip.*; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.internal.events.*; +import org.eclipse.core.internal.localstore.*; +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.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; + +public class SaveManager implements IElementInfoFlattener, IManager, IStringPoolParticipant { + class MasterTable extends Properties { + private static final long serialVersionUID = 1L; + + @Override + public synchronized Object put(Object key, Object value) { + Object prev = super.put(key, value); + if (prev != null && ROOT_SEQUENCE_NUMBER_KEY.equals(key)) { + int prevSeqNum = Integer.parseInt((String) prev); + int currSeqNum = Integer.parseInt((String) value); + if (prevSeqNum > currSeqNum) { + //revert last put operation + super.put(key, prev); + //notify about the problem, do not throw exception but add the exception to know where it occurred + String message = "Cannot set lower sequence number for root (previous: " + prevSeqNum + ", new: " + currSeqNum + "). Ignoring the new value."; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Policy.log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, new IllegalArgumentException(message))); + } + } + return prev; + } + } + + protected static final String ROOT_SEQUENCE_NUMBER_KEY = Path.ROOT.toString() + LocalMetaArea.F_TREE; + protected static final String CLEAR_DELTA_PREFIX = "clearDelta_"; //$NON-NLS-1$ + protected static final String DELTA_EXPIRATION_PREFIX = "deltaExpiration_"; //$NON-NLS-1$ + protected static final int DONE_SAVING = 3; + + /** + * The minimum delay, in milliseconds, between workspace snapshots + */ + private static final long MIN_SNAPSHOT_DELAY = 1000 * 30L; //30 seconds + + /** + * The number of empty operations that are equivalent to a single non- + * trivial operation. + */ + protected static final int NO_OP_THRESHOLD = 20; + + /** constants */ + protected static final int PREPARE_TO_SAVE = 1; + protected static final int ROLLBACK = 4; + protected static final String SAVE_NUMBER_PREFIX = "saveNumber_"; //$NON-NLS-1$ + protected static final int SAVING = 2; + protected ElementTree lastSnap; + protected MasterTable masterTable; + + /** + * A flag indicating that a save operation is occurring. This is a signal + * that snapshot should not be scheduled if a nested operation occurs during + * save. + */ + private volatile boolean isSaving = false; + + /** + * The number of empty (non-changing) operations since the last snapshot. + */ + protected int noopCount = 0; + /** + * The number of non-trivial operations since the last snapshot. + */ + protected int operationCount = 0; + + // Count up the time taken for all saves/snaps on markers and sync info + protected long persistMarkers = 0l; + protected long persistSyncInfo = 0l; + + /** + * In-memory representation of plugins saved state. Maps String (plugin id)-> SavedState. + * This map is accessed from API that is not synchronized, so it requires + * independent synchronization. This is accomplished using a synchronized + * wrapper map. + */ + protected Map savedStates; + + /** + * Ids of plugins that participate on a workspace save. Maps String (plugin id)-> ISaveParticipant. + * This map is accessed from API that is not synchronized, so it requires + * independent synchronization. This is accomplished using a synchronized + * wrapper map. + */ + protected Map saveParticipants; + + protected final DelayedSnapshotJob snapshotJob; + + protected volatile boolean snapshotRequested; + private IStatus snapshotRequestor; + protected Workspace workspace; + //declare debug messages as fields to get sharing + private static final String DEBUG_START = " starting..."; //$NON-NLS-1$ + private static final String DEBUG_FULL_SAVE = "Full save on workspace: "; //$NON-NLS-1$ + private static final String DEBUG_PROJECT_SAVE = "Save on project "; //$NON-NLS-1$ + private static final String DEBUG_SNAPSHOT = "Snapshot: "; //$NON-NLS-1$ + private static final int TREE_BUFFER_SIZE = 1024 * 64;//64KB buffer + + public SaveManager(Workspace workspace) { + this.workspace = workspace; + this.masterTable = new MasterTable(); + this.snapshotJob = new DelayedSnapshotJob(this); + snapshotRequested = false; + snapshotRequestor = null; + saveParticipants = Collections.synchronizedMap(new HashMap(10)); + } + + public ISavedState addParticipant(String pluginId, ISaveParticipant participant) throws CoreException { + // If the plugin was already registered as a save participant we return null + if (saveParticipants.put(pluginId, participant) != null) + return null; + SavedState state = savedStates.get(pluginId); + if (state != null) { + if (isDeltaCleared(pluginId)) { + // this plugin was marked not to receive deltas + state.forgetTrees(); + removeClearDeltaMarks(pluginId); + } else { + try { + // thread safety: (we need to guarantee that the tree is immutable when computing deltas) + // so, the tree inside the saved state needs to be immutable + workspace.prepareOperation(null, null); + workspace.beginOperation(true); + state.newTree = workspace.getElementTree(); + } finally { + workspace.endOperation(null, false); + } + return state; + } + } + // if the plug-in has a previous save number, we return a state, otherwise we return null + if (getSaveNumber(pluginId) > 0) + return new SavedState(workspace, pluginId, null, null); + return null; + } + + protected void broadcastLifecycle(final int lifecycle, Map contexts, final MultiStatus warnings, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", contexts.size()); //$NON-NLS-1$ + for (final Iterator> it = contexts.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + String pluginId = entry.getKey(); + final ISaveParticipant participant = saveParticipants.get(pluginId); + //save participants can be removed concurrently + if (participant == null) { + monitor.worked(1); + continue; + } + final SaveContext context = entry.getValue(); + /* Be extra careful when calling lifecycle method on arbitrary plugin */ + ISafeRunnable code = new ISafeRunnable() { + + @Override + public void handleException(Throwable e) { + String message = Messages.resources_saveProblem; + IStatus status = new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, e); + warnings.add(status); + + /* Remove entry for defective plug-in from this save operation */ + it.remove(); + } + + @Override + public void run() throws Exception { + executeLifecycle(lifecycle, participant, context); + } + }; + SafeRunner.run(code); + monitor.worked(1); + } + } finally { + monitor.done(); + } + } + + /** + * Remove the delta expiration timestamp from the master table, either + * because the saved state has been processed, or the delta has expired. + */ + protected void clearDeltaExpiration(String pluginId) { + masterTable.remove(DELTA_EXPIRATION_PREFIX + pluginId); + } + + protected void cleanMasterTable() { + //remove tree file entries for everything except closed projects + for (Iterator it = masterTable.keySet().iterator(); it.hasNext();) { + String key = (String) it.next(); + if (!key.endsWith(LocalMetaArea.F_TREE)) + continue; + String prefix = key.substring(0, key.length() - LocalMetaArea.F_TREE.length()); + //always save the root tree entry + if (prefix.equals(Path.ROOT.toString())) + continue; + IProject project = workspace.getRoot().getProject(prefix); + if (!project.exists() || project.isOpen()) + it.remove(); + } + IPath location = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES); + IPath backup = workspace.getMetaArea().getBackupLocationFor(location); + try { + saveMasterTable(ISaveContext.FULL_SAVE, backup); + } catch (CoreException e) { + Policy.log(e.getStatus()); + backup.toFile().delete(); + return; + } + if (location.toFile().exists() && !location.toFile().delete()) + return; + try { + saveMasterTable(ISaveContext.FULL_SAVE, location); + } catch (CoreException e) { + Policy.log(e.getStatus()); + location.toFile().delete(); + return; + } + backup.toFile().delete(); + } + + /** + * Marks the current participants to not receive deltas next time they are registered + * as save participants. This is done in order to maintain consistency if we crash + * after a snapshot. It would force plug-ins to rebuild their state. + */ + protected void clearSavedDelta() { + synchronized (saveParticipants) { + for (Iterator i = saveParticipants.keySet().iterator(); i.hasNext();) { + String pluginId = i.next(); + masterTable.setProperty(CLEAR_DELTA_PREFIX + pluginId, "true"); //$NON-NLS-1$ + } + } + } + + /** + * Collects the set of ElementTrees we are still interested in, + * and removes references to any other trees. + */ + protected void collapseTrees(Map contexts) throws CoreException { + //collect trees we're interested in + + //forget saved trees, if they are not used by registered participants + synchronized (savedStates) { + for (Iterator i = contexts.values().iterator(); i.hasNext();) { + SaveContext context = i.next(); + forgetSavedTree(context.getPluginId()); + } + } + + //trees for plugin saved states + ArrayList trees = new ArrayList<>(); + synchronized (savedStates) { + for (Iterator i = savedStates.values().iterator(); i.hasNext();) { + SavedState state = i.next(); + if (state.oldTree != null) { + trees.add(state.oldTree); + } + } + } + + //trees for builders + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) { + IProject project = projects[i]; + if (project.isOpen()) { + ArrayList builderInfos = workspace.getBuildManager().createBuildersPersistentInfo(project); + if (builderInfos != null) { + for (Iterator it = builderInfos.iterator(); it.hasNext();) { + BuilderPersistentInfo info = it.next(); + trees.add(info.getLastBuiltTree()); + } + } + } + } + + //no need to collapse if there are no trees at this point + if (trees.isEmpty()) + return; + + //the complete tree + trees.add(workspace.getElementTree()); + + //collapse the trees + //sort trees in topological order, and set the parent of each + //tree to its parent in the topological ordering. + ElementTree[] treeArray = new ElementTree[trees.size()]; + trees.toArray(treeArray); + ElementTree[] sorted = sortTrees(treeArray); + // if there was a problem sorting the tree, bail on trying to collapse. + // We will be able to GC the layers at a later time. + if (sorted == null) + return; + for (int i = 1; i < sorted.length; i++) + sorted[i].collapseTo(sorted[i - 1]); + } + + protected void commit(Map contexts) throws CoreException { + for (Iterator i = contexts.values().iterator(); i.hasNext();) + i.next().commit(); + } + + /** + * Given a collection of save participants, compute the collection of + * SaveContexts to use during the save lifecycle. + * The keys are plugins and values are SaveContext objects. + */ + protected Map computeSaveContexts(String[] pluginIds, int kind, IProject project) { + HashMap result = new HashMap<>(pluginIds.length); + for (int i = 0; i < pluginIds.length; i++) { + String pluginId = pluginIds[i]; + try { + SaveContext context = new SaveContext(pluginId, kind, project); + result.put(pluginId, context); + } catch (CoreException e) { + // FIXME: should return a status to the user and not just log it + Policy.log(e.getStatus()); + } + } + return result; + } + + /** + * Returns a table mapping having the plug-in id as the key and the old tree + * as the value. + * This table is based on the union of the current savedStates + * and the given table of contexts. The specified tree is used as the tree for + * any newly created saved states. This method is used to compute the set of + * saved states to be written out. + */ + protected Map computeStatesToSave(Map contexts, ElementTree current) { + HashMap result = new HashMap<>(savedStates.size() * 2); + synchronized (savedStates) { + for (Iterator i = savedStates.values().iterator(); i.hasNext();) { + SavedState state = i.next(); + if (state.oldTree != null) + result.put(state.pluginId, state.oldTree); + } + } + for (Iterator i = contexts.values().iterator(); i.hasNext();) { + SaveContext context = i.next(); + if (!context.isDeltaNeeded()) + continue; + String pluginId = context.getPluginId(); + result.put(pluginId, current); + } + return result; + } + + protected void executeLifecycle(int lifecycle, ISaveParticipant participant, SaveContext context) throws CoreException { + switch (lifecycle) { + case PREPARE_TO_SAVE : + participant.prepareToSave(context); + break; + case SAVING : + try { + if (ResourceStats.TRACE_SAVE_PARTICIPANTS) + ResourceStats.startSave(participant); + participant.saving(context); + } finally { + if (ResourceStats.TRACE_SAVE_PARTICIPANTS) + ResourceStats.endSave(); + } + break; + case DONE_SAVING : + participant.doneSaving(context); + break; + case ROLLBACK : + participant.rollback(context); + break; + default : + Assert.isTrue(false, "Invalid save lifecycle code"); //$NON-NLS-1$ + } + } + + public void forgetSavedTree(String pluginId) { + if (pluginId == null) { + synchronized (savedStates) { + for (Iterator i = savedStates.values().iterator(); i.hasNext();) + i.next().forgetTrees(); + } + } else { + SavedState state = savedStates.get(pluginId); + if (state != null) + state.forgetTrees(); + } + } + + /** + * Used in the policy for cleaning up tree's of plug-ins that are not often activated. + */ + protected long getDeltaExpiration(String pluginId) { + String result = masterTable.getProperty(DELTA_EXPIRATION_PREFIX + pluginId); + return (result == null) ? System.currentTimeMillis() : Long.parseLong(result); + } + + protected Properties getMasterTable() { + return masterTable; + } + + public int getSaveNumber(String pluginId) { + String value = masterTable.getProperty(SAVE_NUMBER_PREFIX + pluginId); + return (value == null) ? 0 : Integer.parseInt(value); + } + + protected String[] getSaveParticipantPluginIds() { + synchronized (saveParticipants) { + return saveParticipants.keySet().toArray(new String[saveParticipants.size()]); + } + } + + /** + * Hooks the end of a save operation, for debugging and performance + * monitoring purposes. + */ + private void hookEndSave(int kind, IProject project, long start) { + if (ResourceStats.TRACE_SNAPSHOT && kind == ISaveContext.SNAPSHOT) + ResourceStats.endSnapshot(); + if (Policy.DEBUG_SAVE) { + String endMessage = null; + switch (kind) { + case ISaveContext.FULL_SAVE : + endMessage = DEBUG_FULL_SAVE; + break; + case ISaveContext.SNAPSHOT : + endMessage = DEBUG_SNAPSHOT; + break; + case ISaveContext.PROJECT_SAVE : + endMessage = DEBUG_PROJECT_SAVE + project.getFullPath() + ": "; //$NON-NLS-1$ + break; + } + if (endMessage != null) + Policy.debug(endMessage + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ + } + } + + /** + * Hooks the start of a save operation, for debugging and performance + * monitoring purposes. + */ + private void hookStartSave(int kind, Project project) { + if (ResourceStats.TRACE_SNAPSHOT && kind == ISaveContext.SNAPSHOT) + ResourceStats.startSnapshot(); + if (Policy.DEBUG_SAVE) { + switch (kind) { + case ISaveContext.FULL_SAVE : + Policy.debug(DEBUG_FULL_SAVE + DEBUG_START); + break; + case ISaveContext.SNAPSHOT : + Policy.debug(DEBUG_SNAPSHOT + DEBUG_START); + break; + case ISaveContext.PROJECT_SAVE : + Policy.debug(DEBUG_PROJECT_SAVE + project.getFullPath() + DEBUG_START); + break; + } + } + } + + /** + * Initializes the snapshot mechanism for this workspace. + */ + protected void initSnap(IProgressMonitor monitor) { + // Discard any pending snapshot request. + snapshotJob.cancel(); + // The "lastSnap" tree must be frozen as the exact tree obtained from startup, + // otherwise ensuing snapshot deltas may be based on an incorrect tree (see bug 12575). + lastSnap = workspace.getElementTree(); + lastSnap.immutable(); + workspace.newWorkingTree(); + operationCount = 0; + // Delete the snapshot files, if any. + IPath location = workspace.getMetaArea().getSnapshotLocationFor(workspace.getRoot()); + java.io.File target = location.toFile().getParentFile(); + FilenameFilter filter = new FilenameFilter() { + @Override + public boolean accept(java.io.File dir, String name) { + if (!name.endsWith(LocalMetaArea.F_SNAP)) + return false; + for (int i = 0; i < name.length() - LocalMetaArea.F_SNAP.length(); i++) { + char c = name.charAt(i); + if (c < '0' || c > '9') + return false; + } + return true; + } + }; + String[] candidates = target.list(filter); + if (candidates != null) + removeFiles(target, candidates, Collections. emptyList()); + } + + protected boolean isDeltaCleared(String pluginId) { + String clearDelta = masterTable.getProperty(CLEAR_DELTA_PREFIX + pluginId); + return clearDelta != null && clearDelta.equals("true"); //$NON-NLS-1$ + } + + protected boolean isOldPluginTree(String pluginId) { + // first, check if this plug-ins was marked not to receive a delta + if (isDeltaCleared(pluginId)) + return false; + //see if the plugin is still installed + if (Platform.getBundle(pluginId) == null) + return true; + + //finally see if the delta has past its expiry date + long deltaAge = System.currentTimeMillis() - getDeltaExpiration(pluginId); + return deltaAge > workspace.internalGetDescription().getDeltaExpiration(); + } + + /** + * @see IElementInfoFlattener#readElement(IPath, DataInput) + */ + @Override + public Object readElement(IPath path, DataInput input) throws IOException { + Assert.isNotNull(path); + Assert.isNotNull(input); + // read the flags and pull out the type. + int flags = input.readInt(); + int type = (flags & ICoreConstants.M_TYPE) >> ICoreConstants.M_TYPE_START; + ResourceInfo info = workspace.newElement(type); + info.readFrom(flags, input); + return info; + } + + private void rememberSnapshotRequestor() { + if (Policy.DEBUG_SAVE) + Policy.debug(new RuntimeException("Scheduling workspace snapshot")); //$NON-NLS-1$ + if (snapshotRequestor == null) { + String msg = "The workspace will exit with unsaved changes in this session."; //$NON-NLS-1$ + snapshotRequestor = new ResourceStatus(ICoreConstants.CRASH_DETECTED, msg); + } + } + + /** + * Remove marks from current save participants. This marks prevent them to receive their + * deltas when they register themselves as save participants. + */ + protected void removeClearDeltaMarks() { + synchronized (saveParticipants) { + for (Iterator i = saveParticipants.keySet().iterator(); i.hasNext();) { + String pluginId = i.next(); + removeClearDeltaMarks(pluginId); + } + } + } + + protected void removeClearDeltaMarks(String pluginId) { + masterTable.setProperty(CLEAR_DELTA_PREFIX + pluginId, "false"); //$NON-NLS-1$ + } + + protected void removeFiles(java.io.File root, String[] candidates, List exclude) { + for (int i = 0; i < candidates.length; i++) { + boolean delete = true; + for (ListIterator it = exclude.listIterator(); it.hasNext();) { + String s = it.next(); + if (s.equals(candidates[i])) { + it.remove(); + delete = false; + break; + } + } + if (delete) + new java.io.File(root, candidates[i]).delete(); + } + } + + private void removeGarbage(DataOutputStream output, IPath location, IPath tempLocation) throws IOException { + if (output.size() == 0) { + output.close(); + location.toFile().delete(); + tempLocation.toFile().delete(); + } + } + + public void removeParticipant(String pluginId) { + saveParticipants.remove(pluginId); + } + + protected void removeUnusedSafeTables() { + List valuables = new ArrayList<>(10); + IPath location = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES); + valuables.add(location.lastSegment()); // add master table + for (Enumeration e = masterTable.keys(); e.hasMoreElements();) { + String key = (String) e.nextElement(); + if (key.startsWith(SAVE_NUMBER_PREFIX)) { + String pluginId = key.substring(SAVE_NUMBER_PREFIX.length()); + valuables.add(workspace.getMetaArea().getSafeTableLocationFor(pluginId).lastSegment()); + } + } + java.io.File target = location.toFile().getParentFile(); + String[] candidates = target.list(); + if (candidates == null) + return; + removeFiles(target, candidates, valuables); + } + + protected void removeUnusedTreeFiles() { + // root resource + List valuables = new ArrayList<>(10); + IPath location = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), false); + valuables.add(location.lastSegment()); + java.io.File target = location.toFile().getParentFile(); + FilenameFilter filter = new FilenameFilter() { + @Override + public boolean accept(java.io.File dir, String name) { + return name.endsWith(LocalMetaArea.F_TREE); + } + }; + String[] candidates = target.list(filter); + if (candidates != null) + removeFiles(target, candidates, valuables); + + // projects + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) { + location = workspace.getMetaArea().getTreeLocationFor(projects[i], false); + valuables.add(location.lastSegment()); + target = location.toFile().getParentFile(); + candidates = target.list(filter); + if (candidates != null) + removeFiles(target, candidates, valuables); + } + } + + protected void reportSnapshotRequestor() { + if (snapshotRequestor != null) + Policy.log(snapshotRequestor); + } + + public void requestSnapshot() { + snapshotRequested = true; + } + + /** + * Reset the snapshot mechanism for the non-workspace files. This + * includes the markers and sync info. + */ + protected void resetSnapshots(IResource resource) throws CoreException { + Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT); + String message; + + // delete the snapshot file, if any + java.io.File file = workspace.getMetaArea().getMarkersSnapshotLocationFor(resource).toFile(); + if (file.exists()) + file.delete(); + if (file.exists()) { + message = Messages.resources_resetMarkers; + throw new ResourceException(IResourceStatus.FAILED_DELETE_METADATA, resource.getFullPath(), message, null); + } + + // delete the snapshot file, if any + file = workspace.getMetaArea().getSyncInfoSnapshotLocationFor(resource).toFile(); + if (file.exists()) + file.delete(); + if (file.exists()) { + message = Messages.resources_resetSync; + throw new ResourceException(IResourceStatus.FAILED_DELETE_METADATA, resource.getFullPath(), message, null); + } + + // if we have the workspace root then recursive over the projects. + // only do open projects since closed ones are saved elsewhere + if (resource.getType() == IResource.PROJECT) + return; + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + resetSnapshots(projects[i]); + } + + /** + * Restores the state of this workspace by opening the projects + * which were open when it was last saved. + */ + protected void restore(IProgressMonitor monitor) throws CoreException { + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore workspace: starting..."); //$NON-NLS-1$ + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", 50); //$NON-NLS-1$ + // need to open the tree to restore, but since we're not + // inside an operation, be sure to close it afterwards + workspace.newWorkingTree(); + try { + String msg = Messages.resources_startupProblems; + MultiStatus problems = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, msg, null); + + restoreMasterTable(); + // restore the saved tree and overlay the snapshots if any + restoreTree(Policy.subMonitorFor(monitor, 10)); + restoreSnapshots(Policy.subMonitorFor(monitor, 10)); + + // tolerate failure for non-critical information + // if startup fails, the entire workspace is shot + try { + restoreMarkers(workspace.getRoot(), false, Policy.subMonitorFor(monitor, 10)); + } catch (CoreException e) { + problems.merge(e.getStatus()); + } + try { + restoreSyncInfo(workspace.getRoot(), Policy.subMonitorFor(monitor, 10)); + } catch (CoreException e) { + problems.merge(e.getStatus()); + } + // restore meta info last because it might close a project if its description is not readable + restoreMetaInfo(problems, Policy.subMonitorFor(monitor, 10)); + IProject[] roots = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < roots.length; i++) + ((Project) roots[i]).startup(); + if (!problems.isOK()) + Policy.log(problems); + } finally { + workspace.getElementTree().immutable(); + } + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Restores the contents of this project. Throw + * an exception if the project could not be restored. + * @return true if the project data was restored successfully, + * and false if non-critical problems occurred while restoring. + * @exception CoreException if the project could not be restored. + */ + protected boolean restore(Project project, IProgressMonitor monitor) throws CoreException { + boolean status = true; + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": starting..."); //$NON-NLS-1$ //$NON-NLS-2$ + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", 40); //$NON-NLS-1$ + if (project.isOpen()) { + status = restoreTree(project, Policy.subMonitorFor(monitor, 10)); + } else { + monitor.worked(10); + } + restoreMarkers(project, true, Policy.subMonitorFor(monitor, 10)); + restoreSyncInfo(project, Policy.subMonitorFor(monitor, 10)); + // restore meta info last because it might close a project if its description is not found + restoreMetaInfo(project, Policy.subMonitorFor(monitor, 10)); + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return status; + } + + /** + * Restores the contents of this project from a refresh snapshot, if possible. + * Throws an exception if the snapshot is found but an error occurs when reading + * the file. + * @return true if the project data was restored successfully, + * and false if the refresh snapshot was not found or could not be opened. + * @exception CoreException if an error occurred reading the snapshot file. + */ + protected boolean restoreFromRefreshSnapshot(Project project, IProgressMonitor monitor) throws CoreException { + boolean status = true; + IPath snapshotPath = workspace.getMetaArea().getRefreshLocationFor(project); + java.io.File snapshotFile = snapshotPath.toFile(); + if (!snapshotFile.exists()) + return false; + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": starting..."); //$NON-NLS-1$ //$NON-NLS-2$ + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", 40); //$NON-NLS-1$ + status = restoreTreeFromRefreshSnapshot(project, snapshotFile, Policy.subMonitorFor(monitor, 40)); + if (status) { + // load the project description and set internal description + ProjectDescription description = workspace.getFileSystemManager().read(project, true); + project.internalSetDescription(description, false); + workspace.getMetaArea().clearRefresh(project); + } + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE) + Policy.debug("Restore project " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return status; + } + + /** + * Reads the markers which were originally saved + * for the tree rooted by the given resource. + */ + protected void restoreMarkers(IResource resource, boolean generateDeltas, IProgressMonitor monitor) throws CoreException { + Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT); + long start = System.currentTimeMillis(); + MarkerManager markerManager = workspace.getMarkerManager(); + // when restoring a project, only load markers if it is open + if (resource.isAccessible()) + markerManager.restore(resource, generateDeltas, monitor); + + // if we have the workspace root then restore markers for its projects + if (resource.getType() == IResource.PROJECT) { + if (Policy.DEBUG_RESTORE_MARKERS) { + Policy.debug("Restore Markers for " + resource.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return; + } + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + if (projects[i].isAccessible()) + markerManager.restore(projects[i], generateDeltas, monitor); + if (Policy.DEBUG_RESTORE_MARKERS) { + Policy.debug("Restore Markers for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + protected void restoreMasterTable() throws CoreException { + long start = System.currentTimeMillis(); + masterTable.clear(); + IPath location = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES); + java.io.File target = location.toFile(); + if (!target.exists()) { + location = workspace.getMetaArea().getBackupLocationFor(location); + target = location.toFile(); + if (!target.exists()) + return; + } + try { + SafeChunkyInputStream input = new SafeChunkyInputStream(target); + try { + masterTable.load(input); + } finally { + input.close(); + } + } catch (IOException e) { + String message = Messages.resources_exMasterTable; + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); + } + if (Policy.DEBUG_RESTORE_MASTERTABLE) + Policy.debug("Restore master table for " + location + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Restores the state of this workspace by opening the projects + * which were open when it was last saved. + */ + protected void restoreMetaInfo(MultiStatus problems, IProgressMonitor monitor) { + if (Policy.DEBUG_RESTORE_METAINFO) + Policy.debug("Restore workspace metainfo: starting..."); //$NON-NLS-1$ + long start = System.currentTimeMillis(); + IProject[] roots = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < roots.length; i++) { + //fatal to throw exceptions during startup + try { + restoreMetaInfo((Project) roots[i], monitor); + } catch (CoreException e) { + String message = NLS.bind(Messages.resources_readMeta, roots[i].getName()); + problems.merge(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, roots[i].getFullPath(), message, e)); + } + } + if (Policy.DEBUG_RESTORE_METAINFO) + Policy.debug("Restore workspace metainfo: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Restores the contents of this project. Throw an exception if the + * project description could not be restored. + */ + protected void restoreMetaInfo(Project project, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + ProjectDescription description = null; + CoreException failure = null; + try { + if (project.isOpen()) + description = workspace.getFileSystemManager().read(project, true); + else + //for closed projects, just try to read the legacy .prj file, + //because the project location is stored there. + description = workspace.getMetaArea().readOldDescription(project); + } catch (CoreException e) { + failure = e; + } + // If we had an open project and there was an error reading the description + // from disk, close the project and give it a default description. If the project + // was already closed then just set a default description. + if (description == null) { + description = new ProjectDescription(); + description.setName(project.getName()); + //try to read private metadata and add to the description + workspace.getMetaArea().readPrivateDescription(project, description); + } + project.internalSetDescription(description, false); + if (failure != null) { + // write the project tree ... + writeTree(project, IResource.DEPTH_INFINITE); + // ... and close the project + project.internalClose(monitor); + throw failure; + } + if (Policy.DEBUG_RESTORE_METAINFO) + Policy.debug("Restore metainfo for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Restores the workspace tree from snapshot files in the event + * of a crash. The workspace tree must be open when this method + * is called, and will be open at the end of this method. In the + * event of a crash recovery, the snapshot file is not deleted until + * the next successful save. + */ + protected void restoreSnapshots(IProgressMonitor monitor) { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + IPath snapLocation = workspace.getMetaArea().getSnapshotLocationFor(workspace.getRoot()); + java.io.File localFile = snapLocation.toFile(); + + if (!localFile.exists()) { + // The snapshot corresponding to the current tree version doesn't exist. + // Try the legacy non-versioned snapshot, but ignore it if it is older than + // the tree. + snapLocation = workspace.getMetaArea().getLegacySnapshotLocationFor(workspace.getRoot()); + localFile = snapLocation.toFile(); + if (!localFile.exists() || isSnapshotOlderThanTree(localFile)) { + // If the snapshot file doesn't exist, there was no crash. + // Just initialize the snapshot file and return. + initSnap(Policy.subMonitorFor(monitor, Policy.totalWork / 2)); + return; + } + } + // If we have a snapshot file, the workspace was shutdown without being saved or crashed. + workspace.setCrashed(true); + try { + /* Read each of the snapshots and lay them on top of the current tree.*/ + ElementTree complete = workspace.getElementTree(); + complete.immutable(); + DataInputStream input = new DataInputStream(new SafeChunkyInputStream(localFile)); + try { + WorkspaceTreeReader reader = WorkspaceTreeReader.getReader(workspace, input.readInt()); + complete = reader.readSnapshotTree(input, complete, monitor); + } finally { + FileUtil.safeClose(input); + //reader returned an immutable tree, but since we're inside + //an operation, we must return an open tree + lastSnap = complete; + complete = complete.newEmptyDelta(); + workspace.tree = complete; + } + } catch (Exception e) { + // only log the exception, we should not fail restoring the snapshot + message = Messages.resources_snapRead; + Policy.log(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, null, message, e)); + } + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE_SNAPSHOTS) + Policy.debug("Restore snapshots for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Checks if the given snapshot file is older than the tree file. + * + * @param snapshot the snapshot file to check + * @return {@code true} if the snapshot file is older than the tree file or the tree file + * does not exist + */ + private boolean isSnapshotOlderThanTree(File snapshot) { + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), false); + File tree = treeLocation.toFile(); + if (!tree.exists()) { + treeLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + tree = treeLocation.toFile(); + if (!tree.exists()) + return false; + } + return snapshot.lastModified() < tree.lastModified(); + } + + /** + * Reads the sync info which was originally saved + * for the tree rooted by the given resource. + */ + protected void restoreSyncInfo(IResource resource, IProgressMonitor monitor) throws CoreException { + Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT); + long start = System.currentTimeMillis(); + Synchronizer synchronizer = (Synchronizer) workspace.getSynchronizer(); + // when restoring a project, only load sync info if it is open + if (resource.isAccessible()) + synchronizer.restore(resource, monitor); + + // restore sync info for all projects if we were given the workspace root. + if (resource.getType() == IResource.PROJECT) { + if (Policy.DEBUG_RESTORE_SYNCINFO) { + Policy.debug("Restore SyncInfo for " + resource.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return; + } + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + if (projects[i].isAccessible()) + synchronizer.restore(projects[i], monitor); + if (Policy.DEBUG_RESTORE_SYNCINFO) { + Policy.debug("Restore SyncInfo for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Reads the contents of the tree rooted by the given resource from the + * file system. This method is used when restoring a complete workspace + * after workspace save/shutdown. + * @exception CoreException if the workspace could not be restored. + */ + protected void restoreTree(IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), false); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + if (!treeLocation.toFile().exists() && !tempLocation.toFile().exists()) { + savedStates = Collections.synchronizedMap(new HashMap(10)); + return; + } + try { + DataInputStream input = new DataInputStream(new SafeFileInputStream(treeLocation.toOSString(), tempLocation.toOSString(), TREE_BUFFER_SIZE)); + try { + WorkspaceTreeReader.getReader(workspace, input.readInt()).readTree(input, monitor); + } finally { + input.close(); + } + } catch (IOException e) { + String msg = NLS.bind(Messages.resources_readMeta, treeLocation.toOSString()); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, treeLocation, msg, e); + } + if (Policy.DEBUG_RESTORE_TREE) { + Policy.debug("Restore Tree for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Restores the trees for the builders of this project from the local disk. + * Does nothing if the tree file does not exist (this means the + * project has never been saved). This method is + * used when restoring a saved/closed project. restoreTree(Workspace) is + * used when restoring a complete workspace after workspace save/shutdown. + * @return true if the tree file exists, false otherwise. + * @exception CoreException if the project could not be restored. + */ + protected boolean restoreTree(Project project, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(project, false); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + if (!treeLocation.toFile().exists() && !tempLocation.toFile().exists()) + return false; + DataInputStream input = new DataInputStream(new SafeFileInputStream(treeLocation.toOSString(), tempLocation.toOSString())); + try { + WorkspaceTreeReader reader = WorkspaceTreeReader.getReader(workspace, input.readInt()); + reader.readTree(project, input, Policy.subMonitorFor(monitor, Policy.totalWork)); + } finally { + input.close(); + } + } catch (IOException e) { + message = NLS.bind(Messages.resources_readMeta, project.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, project.getFullPath(), message, e); + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE_TREE) { + Policy.debug("Restore Tree for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return true; + } + + /** + * Restores a tree saved as a refresh snapshot to a specified URI. + * @return true if the snapshot exists, false otherwise. + * @exception CoreException if the project could not be restored. + */ + protected boolean restoreTreeFromRefreshSnapshot(Project project, java.io.File snapshotFile, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + IPath snapshotPath = null; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + InputStream snapIn = new FileInputStream(snapshotFile); + ZipInputStream zip = new ZipInputStream(snapIn); + ZipEntry treeEntry = zip.getNextEntry(); + if (treeEntry == null || !treeEntry.getName().equals("resource-index.tree")) { //$NON-NLS-1$ + zip.close(); + return false; + } + DataInputStream input = new DataInputStream(zip); + try { + WorkspaceTreeReader reader = WorkspaceTreeReader.getReader(workspace, input.readInt(), true); + reader.readTree(project, input, Policy.subMonitorFor(monitor, Policy.totalWork)); + } finally { + input.close(); + zip.close(); + } + } catch (IOException e) { + snapshotPath = new Path(snapshotFile.getPath()); + message = NLS.bind(Messages.resources_readMeta, snapshotPath); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, snapshotPath, message, e); + } finally { + monitor.done(); + } + if (Policy.DEBUG_RESTORE_TREE) { + Policy.debug("Restore Tree for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return true; + } + + class InternalMonitorWrapper extends ProgressMonitorWrapper { + private boolean ignoreCancel; + + public InternalMonitorWrapper(IProgressMonitor monitor) { + super(Policy.monitorFor(monitor)); + } + + public void ignoreCancelState(boolean ignore) { + this.ignoreCancel = ignore; + } + + @Override + public boolean isCanceled() { + return ignoreCancel ? false : super.isCanceled(); + } + } + + public IStatus save(int kind, Project project, IProgressMonitor monitor) throws CoreException { + return save(kind, false, project, monitor); + } + + public IStatus save(int kind, boolean keepConsistencyWhenCanceled, Project project, IProgressMonitor parentMonitor) throws CoreException { + InternalMonitorWrapper monitor = new InternalMonitorWrapper(parentMonitor); + monitor.ignoreCancelState(keepConsistencyWhenCanceled); + try { + isSaving = true; + String message = Messages.resources_saving_0; + monitor.beginTask(message, 7); + message = Messages.resources_saveWarnings; + MultiStatus warnings = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.WARNING, message, null); + ISchedulingRule rule = project != null ? (IResource) project : workspace.getRoot(); + try { + workspace.prepareOperation(rule, monitor); + workspace.beginOperation(false); + hookStartSave(kind, project); + long start = System.currentTimeMillis(); + Map contexts = computeSaveContexts(getSaveParticipantPluginIds(), kind, project); + broadcastLifecycle(PREPARE_TO_SAVE, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + try { + broadcastLifecycle(SAVING, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + switch (kind) { + case ISaveContext.FULL_SAVE : + // save the complete tree and remember all of the required saved states + saveTree(contexts, Policy.subMonitorFor(monitor, 1)); + // reset the snapshot state. + initSnap(null); + snapshotRequestor = null; + //save master table right after saving tree to ensure correct tree number is saved + cleanMasterTable(); + // save all of the markers and all sync info in the workspace + persistMarkers = 0l; + persistSyncInfo = 0l; + visitAndSave(workspace.getRoot()); + monitor.worked(1); + if (Policy.DEBUG_SAVE) { + Policy.debug("Total Save Markers: " + persistMarkers + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + Policy.debug("Total Save Sync Info: " + persistSyncInfo + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + // reset the snap shot files + resetSnapshots(workspace.getRoot()); + //remove unused files + removeUnusedSafeTables(); + removeUnusedTreeFiles(); + + // history pruning can be always canceled + monitor.ignoreCancelState(false); + workspace.getFileSystemManager().getHistoryStore().clean(Policy.subMonitorFor(monitor, 1)); + monitor.ignoreCancelState(keepConsistencyWhenCanceled); + + // write out all metainfo (e.g., workspace/project descriptions) + saveMetaInfo(warnings, Policy.subMonitorFor(monitor, 1)); + break; + case ISaveContext.SNAPSHOT : + snapTree(workspace.getElementTree(), Policy.subMonitorFor(monitor, 1)); + // snapshot the markers and sync info for the workspace + persistMarkers = 0l; + persistSyncInfo = 0l; + visitAndSnap(workspace.getRoot()); + monitor.worked(1); + if (Policy.DEBUG_SAVE) { + Policy.debug("Total Snap Markers: " + persistMarkers + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + Policy.debug("Total Snap Sync Info: " + persistSyncInfo + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + collapseTrees(contexts); + clearSavedDelta(); + // write out all metainfo (e.g., workspace/project descriptions) + saveMetaInfo(warnings, Policy.subMonitorFor(monitor, 1)); + break; + case ISaveContext.PROJECT_SAVE : + writeTree(project, IResource.DEPTH_INFINITE); + monitor.worked(1); + // save markers and sync info + visitAndSave(project); + monitor.worked(1); + // reset the snapshot file + resetSnapshots(project); + IStatus result = saveMetaInfo(project, null); + if (!result.isOK()) + warnings.merge(result); + monitor.worked(1); + break; + } + // save contexts + commit(contexts); + if (kind == ISaveContext.FULL_SAVE) + removeClearDeltaMarks(); + //this must be done after committing save contexts to update participant save numbers + saveMasterTable(kind); + broadcastLifecycle(DONE_SAVING, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + hookEndSave(kind, project, start); + return warnings; + } catch (CoreException e) { + broadcastLifecycle(ROLLBACK, contexts, warnings, Policy.subMonitorFor(monitor, 1)); + // rollback ResourcesPlugin master table + restoreMasterTable(); + throw e; // re-throw + } + } catch (OperationCanceledException e) { + workspace.getWorkManager().operationCanceled(); + throw e; + } finally { + workspace.endOperation(rule, false); + } + } finally { + isSaving = false; + monitor.done(); + } + } + + protected void saveMasterTable(int kind) throws CoreException { + saveMasterTable(kind, workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES)); + } + + protected void saveMasterTable(int kind, IPath location) throws CoreException { + long start = System.currentTimeMillis(); + java.io.File target = location.toFile(); + try { + if (kind == ISaveContext.FULL_SAVE || kind == ISaveContext.SNAPSHOT) + validateMasterTableBeforeSave(target); + SafeChunkyOutputStream output = new SafeChunkyOutputStream(target); + try { + masterTable.store(output, "master table"); //$NON-NLS-1$ + output.succeed(); + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (IOException e) { + throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, NLS.bind(Messages.resources_exSaveMaster, location.toOSString()), e); + } + if (Policy.DEBUG_SAVE_MASTERTABLE) + Policy.debug("Save master table for " + location + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Writes the metainfo (e.g. descriptions) of the given workspace and + * all projects to the local disk. + */ + protected void saveMetaInfo(MultiStatus problems, IProgressMonitor monitor) throws CoreException { + if (Policy.DEBUG_SAVE_METAINFO) + Policy.debug("Save workspace metainfo: starting..."); //$NON-NLS-1$ + long start = System.currentTimeMillis(); + // save preferences (workspace description, path variables, etc) + ResourcesPlugin.getPlugin().savePluginPreferences(); + // save projects' meta info + IProject[] roots = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < roots.length; i++) + if (roots[i].isAccessible()) { + IStatus result = saveMetaInfo((Project) roots[i], null); + if (!result.isOK()) + problems.merge(result); + } + if (Policy.DEBUG_SAVE_METAINFO) + Policy.debug("Save workspace metainfo: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Ensures that the project meta-info is saved. The project meta-info + * is usually saved as soon as it changes, so this is just a sanity check + * to make sure there is something on disk before we shutdown. + * + * @return Status object containing non-critical warnings, or an OK status. + */ + protected IStatus saveMetaInfo(Project project, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + //if there is nothing on disk, write the description + if (!workspace.getFileSystemManager().hasSavedDescription(project)) { + workspace.getFileSystemManager().writeSilently(project); + String msg = NLS.bind(Messages.resources_missingProjectMetaRepaired, project.getName()); + return new ResourceStatus(IResourceStatus.MISSING_DESCRIPTION_REPAIRED, project.getFullPath(), msg); + } + if (Policy.DEBUG_SAVE_METAINFO) + Policy.debug("Save metainfo for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return Status.OK_STATUS; + } + + /** + * Writes a snapshot of project refresh information to the specified + * location. + * @param project the project to write a refresh snapshot for + * @param monitor progress monitor + * @exception CoreException if there is a problem writing the snapshot. + */ + public void saveRefreshSnapshot(Project project, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { + IFileStore store = EFS.getStore(snapshotLocation); + IPath snapshotPath = new Path(snapshotLocation.getPath()); + java.io.File tmpTree = null; + try { + tmpTree = java.io.File.createTempFile("tmp", ".tree"); //$NON-NLS-1$//$NON-NLS-2$ + } catch (IOException e) { + throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, snapshotPath, Messages.resources_copyProblem, e); + } + ZipOutputStream out = null; + try { + FileOutputStream fis = new FileOutputStream(tmpTree); + DataOutputStream output = new DataOutputStream(fis); + try { + output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeTree(project, output, monitor); + output.close(); + } finally { + FileUtil.safeClose(output); + } + OutputStream snapOut = store.openOutputStream(EFS.NONE, monitor); + out = new ZipOutputStream(snapOut); + out.setLevel(Deflater.BEST_COMPRESSION); + ZipEntry e = new ZipEntry("resource-index.tree"); //$NON-NLS-1$ + out.putNextEntry(e); + int read = 0; + byte[] buffer = new byte[4096]; + InputStream in = new FileInputStream(tmpTree); + try { + while ((read = in.read(buffer)) >= 0) { + out.write(buffer, 0, read); + } + out.closeEntry(); + in.close(); + } finally { + FileUtil.safeClose(in); + } + out.close(); + } catch (IOException e) { + throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, snapshotPath, Messages.resources_copyProblem, e); + } finally { + FileUtil.safeClose(out); + if (tmpTree != null) + tmpTree.delete(); + } + } + + /** + * Writes the current state of the entire workspace tree to disk. + * This is used during workspace save. saveTree(Project) + * is used to save the state of an individual project. + * @exception CoreException if there is a problem writing the tree to disk. + */ + protected void saveTree(Map contexts, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(workspace.getRoot(), true); + try { + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + DataOutputStream output = new DataOutputStream(new SafeFileOutputStream(treeLocation.toOSString(), tempLocation.toOSString())); + try { + output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeTree(computeStatesToSave(contexts, workspace.getElementTree()), output, monitor); + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (Exception e) { + String msg = NLS.bind(Messages.resources_writeWorkspaceMeta, treeLocation); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, Path.ROOT, msg, e); + } + if (Policy.DEBUG_SAVE_TREE) + Policy.debug("Save Workspace Tree: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Should only be used for read purposes. + */ + void setPluginsSavedState(HashMap savedStates) { + this.savedStates = Collections.synchronizedMap(savedStates); + } + + protected void setSaveNumber(String pluginId, int number) { + masterTable.setProperty(SAVE_NUMBER_PREFIX + pluginId, Integer.toString(number)); + } + + @Override + public void shareStrings(StringPool pool) { + lastSnap.shareStrings(pool); + } + + @Override + public void shutdown(final IProgressMonitor monitor) { + // do a last snapshot if it was scheduled + // we force it in the same thread because it would not + // help if the job runs after we close the workspace + int state = snapshotJob.getState(); + if (state == Job.WAITING || state == Job.SLEEPING) + // we cannot pass null to Job#run + snapshotJob.run(Policy.monitorFor(monitor)); + // cancel the snapshot job + snapshotJob.cancel(); + } + + /** + * Performs a snapshot if one is deemed necessary. + * Encapsulates rules for determining when a snapshot is needed. + * This should be called at the end of every top level operation. + */ + public void snapshotIfNeeded(boolean hasTreeChanges) { + // never schedule a snapshot while save is occurring. + if (isSaving) + return; + if (snapshotRequested || operationCount >= workspace.internalGetDescription().getOperationsPerSnapshot()) { + rememberSnapshotRequestor(); + if (snapshotJob.getState() == Job.NONE) + snapshotJob.schedule(); + else + snapshotJob.wakeUp(); + } else { + if (hasTreeChanges) { + operationCount++; + if (snapshotJob.getState() == Job.NONE) { + rememberSnapshotRequestor(); + long interval = workspace.internalGetDescription().getSnapshotInterval(); + snapshotJob.schedule(Math.max(interval, MIN_SNAPSHOT_DELAY)); + } + } else { + //only increment the operation count if we've had a sufficient number of no-ops + if (++noopCount > NO_OP_THRESHOLD) { + operationCount++; + noopCount = 0; + } + } + } + } + + /** + * Performs a snapshot of the workspace tree. + */ + protected void snapTree(ElementTree tree, IProgressMonitor monitor) throws CoreException { + long start = System.currentTimeMillis(); + monitor = Policy.monitorFor(monitor); + String message; + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + //the tree must be immutable + tree.immutable(); + // don't need to snapshot if there are no changes + if (tree == lastSnap) + return; + operationCount = 0; + IPath snapPath = workspace.getMetaArea().getSnapshotLocationFor(workspace.getRoot()); + ElementTreeWriter writer = new ElementTreeWriter(this); + java.io.File localFile = snapPath.toFile(); + try { + SafeChunkyOutputStream safeStream = new SafeChunkyOutputStream(localFile); + DataOutputStream out = new DataOutputStream(safeStream); + try { + out.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeWorkspaceFields(out, monitor); + writer.writeDelta(tree, lastSnap, Path.ROOT, ElementTreeWriter.D_INFINITE, out, ResourceComparator.getSaveComparator()); + safeStream.succeed(); + out.close(); + } finally { + FileUtil.safeClose(out); + } + } catch (IOException e) { + message = NLS.bind(Messages.resources_writeWorkspaceMeta, localFile.getAbsolutePath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, Path.ROOT, message, e); + } + lastSnap = tree; + } finally { + monitor.done(); + } + if (Policy.DEBUG_SAVE_TREE) + Policy.debug("Snapshot Workspace Tree: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Sorts the given array of trees so that the following rules are true: + * - The first tree has no parent + * - No tree has an ancestor with a greater index in the array. + * If there are no missing parents in the given trees array, this means + * that in the resulting array, the i'th tree's parent will be tree i-1. + * The input tree array may contain duplicate trees. + */ + protected ElementTree[] sortTrees(ElementTree[] trees) { + /* the sorted list */ + int numTrees = trees.length; + ElementTree[] sorted = new ElementTree[numTrees]; + + /* first build a table of ElementTree -> List of Integers(indices in trees array) */ + Map> table = new HashMap<>(numTrees * 2 + 1); + for (int i = 0; i < trees.length; i++) { + List indices = table.get(trees[i]); + if (indices == null) { + indices = new ArrayList<>(10); + table.put(trees[i], indices); + } + indices.add(i); + } + + /* find the oldest tree (a descendent of all other trees) */ + ElementTree oldest = trees[ElementTree.findOldest(trees)]; + + /** + * Walk through the chain of trees from oldest to newest, + * adding them to the sorted list as we go. + */ + int i = numTrees - 1; + while (i >= 0) { + /* add all instances of the current oldest tree to the sorted list */ + List indices = table.remove(oldest); + for (Enumeration e = Collections.enumeration(indices); e.hasMoreElements();) { + e.nextElement(); + sorted[i] = oldest; + i--; + } + if (i >= 0) { + /* find the next tree in the list */ + ElementTree parent = oldest.getParent(); + while (parent != null && table.get(parent) == null) { + parent = parent.getParent(); + } + if (parent == null) { + IStatus status = new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, "null parent found while collapsing trees", null); //$NON-NLS-1$ + Policy.log(status); + return null; + } + oldest = parent; + } + } + return sorted; + } + + @Override + public void startup(IProgressMonitor monitor) throws CoreException { + restore(monitor); + java.io.File table = workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES).toFile(); + if (!table.exists()) + table.getParentFile().mkdirs(); + } + + /** + * Update the expiration time for the given plug-in's tree. If the tree was never + * loaded, use the current value in the master table. If the tree has been loaded, + * use the provided new timestamp. + * + * The timestamp is used in the policy for cleaning up tree's of plug-ins that are + * not often activated. + */ + protected void updateDeltaExpiration(String pluginId) { + String key = DELTA_EXPIRATION_PREFIX + pluginId; + if (!masterTable.containsKey(key)) + masterTable.setProperty(key, Long.toString(System.currentTimeMillis())); + } + + private void validateMasterTableBeforeSave(java.io.File target) throws IOException { + if (target.exists()) { + MasterTable previousMasterTable = new MasterTable(); + SafeChunkyInputStream input = new SafeChunkyInputStream(target); + try { + previousMasterTable.load(input); + String stringValue = previousMasterTable.getProperty(ROOT_SEQUENCE_NUMBER_KEY); + // if there was a full save, then there must be a non-null entry for root + if (stringValue != null) { + int valueInFile = Integer.parseInt(stringValue); + int valueInMemory = Integer.parseInt(masterTable.getProperty(ROOT_SEQUENCE_NUMBER_KEY)); + // new master table must provide greater or equal sequence number for root + // throw exception if new value is lower than previous one - we cannot allow to desynchronize master table on disk + String message = "Cannot set lower sequence number for root (previous: " + valueInFile + ", new: " + valueInMemory + "). Location: " + target.getAbsolutePath(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Assert.isLegal(valueInMemory >= valueInFile, message); + } + } finally { + input.close(); + } + } + } + + /** + * Visit the given resource (to depth infinite) and write out extra information + * like markers and sync info. To be called during a full save and project save. + * + * FIXME: This method is ugly. Fix it up and look at merging with #visitAndSnap + */ + public void visitAndSave(final IResource root) throws CoreException { + // Ensure we have either a project or the workspace root + Assert.isLegal(root.getType() == IResource.ROOT || root.getType() == IResource.PROJECT); + // only write out info for accessible resources + if (!root.isAccessible()) + return; + + // Setup variables + final Synchronizer synchronizer = (Synchronizer) workspace.getSynchronizer(); + final MarkerManager markerManager = workspace.getMarkerManager(); + IPath markersLocation = workspace.getMetaArea().getMarkersLocationFor(root); + IPath markersTempLocation = workspace.getMetaArea().getBackupLocationFor(markersLocation); + IPath syncInfoLocation = workspace.getMetaArea().getSyncInfoLocationFor(root); + IPath syncInfoTempLocation = workspace.getMetaArea().getBackupLocationFor(syncInfoLocation); + final List writtenTypes = new ArrayList<>(5); + final List writtenPartners = new ArrayList<>(synchronizer.registry.size()); + DataOutputStream o1 = null; + DataOutputStream o2 = null; + String message; + + // Create the output streams + try { + o1 = new DataOutputStream(new SafeFileOutputStream(markersLocation.toOSString(), markersTempLocation.toOSString())); + // we don't store the sync info for the workspace root so don't create + // an empty file + if (root.getType() != IResource.ROOT) + o2 = new DataOutputStream(new SafeFileOutputStream(syncInfoLocation.toOSString(), syncInfoTempLocation.toOSString())); + } catch (IOException e) { + FileUtil.safeClose(o1); + FileUtil.safeClose(o2); + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } + + final DataOutputStream markersOutput = o1; + final DataOutputStream syncInfoOutput = o2; + // The following 2 piece array will hold a running total of the times + // taken to save markers and syncInfo respectively. This will cut down + // on the number of statements printed out as we would get 2 statements + // for each resource otherwise. + final long[] saveTimes = new long[2]; + + // Create the visitor + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) { + ResourceInfo info = (ResourceInfo) elementContents; + if (info != null) { + try { + // save the markers + long start = System.currentTimeMillis(); + markerManager.save(info, requestor, markersOutput, writtenTypes); + long markerSaveTime = System.currentTimeMillis() - start; + saveTimes[0] += markerSaveTime; + persistMarkers += markerSaveTime; + // save the sync info - if we have the workspace root then the output stream will be null + if (syncInfoOutput != null) { + start = System.currentTimeMillis(); + synchronizer.saveSyncInfo(info, requestor, syncInfoOutput, writtenPartners); + long syncInfoSaveTime = System.currentTimeMillis() - start; + saveTimes[1] += syncInfoSaveTime; + persistSyncInfo += syncInfoSaveTime; + } + } catch (IOException e) { + throw new WrappedRuntimeException(e); + } + } + // don't continue if the current resource is the workspace root, only continue for projects + return root.getType() != IResource.ROOT; + } + }; + + // Call the visitor + try { + try { + new ElementTreeIterator(workspace.getElementTree(), root.getFullPath()).iterate(visitor); + } catch (WrappedRuntimeException e) { + throw (IOException) e.getTargetException(); + } + if (Policy.DEBUG_SAVE_MARKERS) + Policy.debug("Save Markers for " + root.getFullPath() + ": " + saveTimes[0] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (Policy.DEBUG_SAVE_SYNCINFO) + Policy.debug("Save SyncInfo for " + root.getFullPath() + ": " + saveTimes[1] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + removeGarbage(markersOutput, markersLocation, markersTempLocation); + // if we have the workspace root the output stream will be null and we + // don't have to perform cleanup code + if (syncInfoOutput != null) { + removeGarbage(syncInfoOutput, syncInfoLocation, syncInfoTempLocation); + syncInfoOutput.close(); + } + markersOutput.close(); + } catch (IOException e) { + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } finally { + FileUtil.safeClose(markersOutput); + FileUtil.safeClose(syncInfoOutput); + } + + // recurse over the projects in the workspace if we were given the workspace root + if (root.getType() == IResource.PROJECT) + return; + IProject[] projects = ((IWorkspaceRoot) root).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + visitAndSave(projects[i]); + } + + /** + * Visit the given resource (to depth infinite) and write out extra information + * like markers and sync info. To be called during a snapshot + * + * FIXME: This method is ugly. Fix it up and look at merging with #visitAndSnap + */ + public void visitAndSnap(final IResource root) throws CoreException { + // Ensure we have either a project or the workspace root + Assert.isLegal(root.getType() == IResource.ROOT || root.getType() == IResource.PROJECT); + // only write out info for accessible resources + if (!root.isAccessible()) + return; + + // Setup variables + final Synchronizer synchronizer = (Synchronizer) workspace.getSynchronizer(); + final MarkerManager markerManager = workspace.getMarkerManager(); + IPath markersLocation = workspace.getMetaArea().getMarkersSnapshotLocationFor(root); + IPath syncInfoLocation = workspace.getMetaArea().getSyncInfoSnapshotLocationFor(root); + SafeChunkyOutputStream safeMarkerStream = null; + SafeChunkyOutputStream safeSyncInfoStream = null; + DataOutputStream o1 = null; + DataOutputStream o2 = null; + String message; + + // Create the output streams + try { + safeMarkerStream = new SafeChunkyOutputStream(markersLocation.toFile()); + o1 = new DataOutputStream(safeMarkerStream); + // we don't store the sync info for the workspace root so don't create + // an empty file + if (root.getType() != IResource.ROOT) { + safeSyncInfoStream = new SafeChunkyOutputStream(syncInfoLocation.toFile()); + o2 = new DataOutputStream(safeSyncInfoStream); + } + } catch (IOException e) { + FileUtil.safeClose(o1); + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } + + final DataOutputStream markersOutput = o1; + final DataOutputStream syncInfoOutput = o2; + int markerFileSize = markersOutput.size(); + int syncInfoFileSize = safeSyncInfoStream == null ? -1 : syncInfoOutput.size(); + // The following 2 piece array will hold a running total of the times + // taken to save markers and syncInfo respectively. This will cut down + // on the number of statements printed out as we would get 2 statements + // for each resource otherwise. + final long[] snapTimes = new long[2]; + + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) { + ResourceInfo info = (ResourceInfo) elementContents; + if (info != null) { + try { + // save the markers + long start = System.currentTimeMillis(); + markerManager.snap(info, requestor, markersOutput); + long markerSnapTime = System.currentTimeMillis() - start; + snapTimes[0] += markerSnapTime; + persistMarkers += markerSnapTime; + // save the sync info - if we have the workspace root then the output stream will be null + if (syncInfoOutput != null) { + start = System.currentTimeMillis(); + synchronizer.snapSyncInfo(info, requestor, syncInfoOutput); + long syncInfoSnapTime = System.currentTimeMillis() - start; + snapTimes[1] += syncInfoSnapTime; + persistSyncInfo += syncInfoSnapTime; + } + } catch (IOException e) { + throw new WrappedRuntimeException(e); + } + } + // don't continue if the current resource is the workspace root, only continue for projects + return root.getType() != IResource.ROOT; + } + }; + + try { + // Call the visitor + try { + new ElementTreeIterator(workspace.getElementTree(), root.getFullPath()).iterate(visitor); + } catch (WrappedRuntimeException e) { + throw (IOException) e.getTargetException(); + } + if (Policy.DEBUG_SAVE_MARKERS) + Policy.debug("Snap Markers for " + root.getFullPath() + ": " + snapTimes[0] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (Policy.DEBUG_SAVE_SYNCINFO) + Policy.debug("Snap SyncInfo for " + root.getFullPath() + ": " + snapTimes[1] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (markerFileSize != markersOutput.size()) + safeMarkerStream.succeed(); + if (safeSyncInfoStream != null && syncInfoFileSize != syncInfoOutput.size()) { + safeSyncInfoStream.succeed(); + syncInfoOutput.close(); + } + markersOutput.close(); + } catch (IOException e) { + message = NLS.bind(Messages.resources_writeMeta, root.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, root.getFullPath(), message, e); + } finally { + FileUtil.safeClose(markersOutput); + FileUtil.safeClose(syncInfoOutput); + } + + // recurse over the projects in the workspace if we were given the workspace root + if (root.getType() == IResource.PROJECT) + return; + IProject[] projects = ((IWorkspaceRoot) root).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + visitAndSnap(projects[i]); + } + + /** + * Writes out persistent information about all builders for which a last built + * tree is available. File format is: + * int - number of builders + * for each builder: + * UTF - project name + * UTF - fully qualified builder extension name + * int - number of interesting projects for builder + * For each interesting project: + * UTF - interesting project name + */ + private void writeBuilderPersistentInfo(DataOutputStream output, List builders, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + // write the number of builders we are saving + int numBuilders = builders.size(); + output.writeInt(numBuilders); + for (int i = 0; i < numBuilders; i++) { + BuilderPersistentInfo info = builders.get(i); + output.writeUTF(info.getProjectName()); + output.writeUTF(info.getBuilderName()); + // write interesting projects + IProject[] interestingProjects = info.getInterestingProjects(); + output.writeInt(interestingProjects.length); + for (int j = 0; j < interestingProjects.length; j++) + output.writeUTF(interestingProjects[j].getName()); + } + } finally { + monitor.done(); + } + } + + @Override + public void writeElement(IPath path, Object element, DataOutput output) throws IOException { + Assert.isNotNull(path); + Assert.isNotNull(element); + Assert.isNotNull(output); + ResourceInfo info = (ResourceInfo) element; + output.writeInt(info.getFlags()); + info.writeTo(output); + } + + /** + * Discovers the trees which need to be saved for the passed in project's builders. + * In a pre-3.7 workspace, only one tree is saved per builder. + * Since 3.7 one tree may be persisted per build configuration per multi-config builder. + * + * We still provide one tree per builder first so the workspace can be opened in an older Eclipse. + * Newer eclipses will be able to load the additional per-configuration trees. + * @param project project to fetch builder trees for + * @param trees list of trees to be persisted + * @param builderInfos list of builder infos; one per builder + * @param configNames configuration names persisted for builder infos above + * @param additionalTrees remaining trees to be persisted for other configurations + * @param additionalBuilderInfos remaining builder infos for other configurations + * @param additionalConfigNames configuration names of the remaining per-configuration trees + * @throws CoreException + */ + private void getTreesToSave(IProject project, List trees, List builderInfos, List configNames, List additionalTrees, List additionalBuilderInfos, List additionalConfigNames) throws CoreException { + if (project.isOpen()) { + String activeConfigName = project.getActiveBuildConfig().getName(); + List infos = workspace.getBuildManager().createBuildersPersistentInfo(project); + if (infos != null) { + for (Iterator it = infos.iterator(); it.hasNext();) { + BuilderPersistentInfo info = it.next(); + // Nothing to persist if there isn't a previous delta tree. + // There used to be code which serialized the current workspace tree + // but this will result in the next build of the builder getting an empty delta... + if (info.getLastBuiltTree() == null) + continue; + + // Add to the correct list of builders info and add to the configuration names + String configName = info.getConfigName() == null ? activeConfigName : info.getConfigName(); + if (configName.equals(activeConfigName)) { + // Serializes the active configurations's build tree + // TODO could probably do better by serializing the 'oldest' tree + builderInfos.add(info); + configNames.add(configName); + trees.add(info.getLastBuiltTree()); + } else { + additionalBuilderInfos.add(info); + additionalConfigNames.add(configName); + additionalTrees.add(info.getLastBuiltTree()); + } + } + } + } + } + + /** + * Attempts to save plugin info, builder info and build states for all projects + * in the workspace. + * + * The following is written to the output stream: + *
                    + *
                  • Workspace information
                  • + *
                  • A list of plugin info
                  • + *
                  • Builder info for all the builders for each project's active build config
                  • + *
                  • Workspace trees for all plugins and builders
                  • + *
                  • And since 3.7:
                  • + *
                  • Builder info for all the builders of all the other project's buildConfigs
                  • + *
                  • The names of the buildConfigs for each of the builders
                  • + *
                  + * This format is designed to work with WorkspaceTreeReader versions 2. + * + * @see WorkspaceTreeReader_2 + */ + protected void writeTree(Map statesToSave, DataOutputStream output, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + boolean wasImmutable = false; + try { + // Create an array of trees to save. Ensure that the current one is in the list + ElementTree current = workspace.getElementTree(); + wasImmutable = current.isImmutable(); + current.immutable(); + ArrayList trees = new ArrayList<>(statesToSave.size() * 2); // pick a number + monitor.worked(Policy.totalWork * 10 / 100); + + // write out the workspace fields + writeWorkspaceFields(output, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + // save plugin info + output.writeInt(statesToSave.size()); // write the number of plugins we are saving + for (Map.Entry entry : statesToSave.entrySet()) { + String pluginId = entry.getKey(); + output.writeUTF(pluginId); + trees.add(entry.getValue()); // tree + updateDeltaExpiration(pluginId); + } + monitor.worked(Policy.totalWork * 10 / 100); + + // Get the the builder info and configuration names, and add all the associated workspace trees in the correct order + IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List builderInfos = new ArrayList<>(projects.length * 2); + List configNames = new ArrayList<>(projects.length); + List additionalTrees = new ArrayList<>(projects.length * 2); + List additionalBuilderInfos = new ArrayList<>(projects.length * 2); + List additionalConfigNames = new ArrayList<>(projects.length); + for (int i = 0; i < projects.length; i++) + getTreesToSave(projects[i], trees, builderInfos, configNames, additionalTrees, additionalBuilderInfos, additionalConfigNames); + + // Save the version 2 builders info + writeBuilderPersistentInfo(output, builderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 10 / 100)); + + // Builder infos of non-active configurations are persisted after the active + // configuration's builder infos. So, their trees have to follow the same order. + trees.addAll(additionalTrees); + + // add the current tree in the list as the last tree in the chain + trees.add(current); + + /* save the forest! */ + ElementTreeWriter writer = new ElementTreeWriter(this); + ElementTree[] treesToSave = trees.toArray(new ElementTree[trees.size()]); + writer.writeDeltaChain(treesToSave, Path.ROOT, ElementTreeWriter.D_INFINITE, output, ResourceComparator.getSaveComparator()); + monitor.worked(Policy.totalWork * 40 / 100); + + // Since 3.7: Save the additional builders info + writeBuilderPersistentInfo(output, additionalBuilderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 10 / 100)); + + // Save the configuration names for the builders in the order they were saved + for (Iterator it = configNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + for (Iterator it = additionalConfigNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + } finally { + if (!wasImmutable) + workspace.newWorkingTree(); + } + } finally { + monitor.done(); + } + } + + /** + * Attempts to save all the trees for the given project. This includes the current + * workspace tree and a tree for each builder that has previously built state information. + * + * The following is written to the output stream: + *
                    + *
                  • Builder info for all the builders for the project's active build configuration
                  • + *
                  • Workspace trees for all the project's builders
                  • + *
                  • Since 3.7:
                  • + *
                  • Builder info for all the builders of all the other project's buildConfigs
                  • + *
                  • Name of the project's buildConfigs
                  • + *
                  + * This format is designed to work with WorkspaceTreeReader versions 2. + * + * @throws IOException if anything went wrong during save. + * @see WorkspaceTreeReader_2 + */ + protected void writeTree(Project project, DataOutputStream output, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + boolean wasImmutable = false; + try { + // Create an array of trees to save and ensure that the current one is immutable before we add other trees + ElementTree current = workspace.getElementTree(); + wasImmutable = current.isImmutable(); + current.immutable(); + List trees = new ArrayList<>(2); + monitor.worked(Policy.totalWork * 10 / 100); + + // Get the the builder info and configuration names, and add all the associated workspace trees in the correct order + List configNames = new ArrayList<>(5); + List builderInfos = new ArrayList<>(5); + List additionalConfigNames = new ArrayList<>(5); + List additionalBuilderInfos = new ArrayList<>(5); + List additionalTrees = new ArrayList<>(5); + getTreesToSave(project, trees, builderInfos, configNames, additionalTrees, additionalBuilderInfos, additionalConfigNames); + + // Save the version 2 builders info + writeBuilderPersistentInfo(output, builderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 20 / 100)); + + // Builder infos of non-active configurations are persisted after the active + // configuration's builder infos. So, their trees have to follow the same order. + trees.addAll(additionalTrees); + + // Add the current tree in the list as the last tree in the chain + trees.add(current); + + // Save the trees + ElementTreeWriter writer = new ElementTreeWriter(this); + ElementTree[] treesToSave = trees.toArray(new ElementTree[trees.size()]); + writer.writeDeltaChain(treesToSave, project.getFullPath(), ElementTreeWriter.D_INFINITE, output, ResourceComparator.getSaveComparator()); + monitor.worked(Policy.totalWork * 50 / 100); + + // Since 3.7: Save the builders info and get the workspace trees associated with those builders + writeBuilderPersistentInfo(output, additionalBuilderInfos, Policy.subMonitorFor(monitor, Policy.totalWork * 20 / 100)); + + // Save configuration names for the builders in the order they were saved + for (Iterator it = configNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + for (Iterator it = additionalConfigNames.iterator(); it.hasNext();) + output.writeUTF(it.next()); + } finally { + if (!wasImmutable) + workspace.newWorkingTree(); + } + } finally { + monitor.done(); + } + } + + protected void writeTree(Project project, int depth) throws CoreException { + long start = System.currentTimeMillis(); + IPath treeLocation = workspace.getMetaArea().getTreeLocationFor(project, true); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(treeLocation); + try { + SafeFileOutputStream safe = new SafeFileOutputStream(treeLocation.toOSString(), tempLocation.toOSString()); + DataOutputStream output = new DataOutputStream(safe); + try { + output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2); + writeTree(project, output, null); + output.close(); + } finally { + FileUtil.safeClose(output); + } + } catch (IOException e) { + String msg = NLS.bind(Messages.resources_writeMeta, project.getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, treeLocation, msg, e); + } + if (Policy.DEBUG_SAVE_TREE) + Policy.debug("Save tree for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + protected void writeWorkspaceFields(DataOutputStream output, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + // save the next node id + output.writeLong(workspace.nextNodeId); + // save the modification stamp (no longer used) + output.writeLong(0L); + // save the marker id counter + output.writeLong(workspace.nextMarkerId); + // save the registered sync partners in the synchronizer + ((Synchronizer) workspace.getSynchronizer()).savePartners(output); + } finally { + monitor.done(); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java new file mode 100644 index 0000000000..d66f201099 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.internal.events.ResourceDelta; +import org.eclipse.core.internal.events.ResourceDeltaFactory; +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.ISchedulingRule; + +/** + * Standard implementation of the ISavedState interface. + */ +public class SavedState implements ISavedState { + ElementTree oldTree; + ElementTree newTree; + SafeFileTable fileTable; + String pluginId; + Workspace workspace; + + SavedState(Workspace workspace, String pluginId, ElementTree oldTree, ElementTree newTree) throws CoreException { + this.workspace = workspace; + this.pluginId = pluginId; + this.newTree = newTree; + this.oldTree = oldTree; + this.fileTable = restoreFileTable(); + } + + void forgetTrees() { + newTree = null; + oldTree = null; + workspace.saveManager.clearDeltaExpiration(pluginId); + } + + @Override + public int getSaveNumber() { + return workspace.getSaveManager().getSaveNumber(pluginId); + } + + protected SafeFileTable getFileTable() { + return fileTable; + } + + protected SafeFileTable restoreFileTable() throws CoreException { + if (fileTable == null) + fileTable = new SafeFileTable(pluginId); + return fileTable; + } + + @Override + public IPath lookup(IPath file) { + return getFileTable().lookup(file); + } + + @Override + public IPath[] getFiles() { + return getFileTable().getFiles(); + } + + @Override + public void processResourceChangeEvents(IResourceChangeListener listener) { + try { + final ISchedulingRule rule = workspace.getRoot(); + try { + workspace.prepareOperation(rule, null); + if (oldTree == null || newTree == null) + return; + workspace.beginOperation(true); + ResourceDelta delta = ResourceDeltaFactory.computeDelta(workspace, oldTree, newTree, Path.ROOT, -1); + forgetTrees(); // free trees to prevent memory leak + workspace.getNotificationManager().broadcastChanges(listener, IResourceChangeEvent.POST_BUILD, delta); + } finally { + workspace.endOperation(rule, false); + } + } catch (CoreException e) { + // this is unlikely to happen, so, just log it + Policy.log(e); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java new file mode 100644 index 0000000000..c9ed601627 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.osgi.util.NLS; + +/** + * This class is used to read sync info from disk. Subclasses implement + * version specific reading code. + */ +public class SyncInfoReader { + protected Workspace workspace; + protected Synchronizer synchronizer; + + public SyncInfoReader(Workspace workspace, Synchronizer synchronizer) { + super(); + this.workspace = workspace; + this.synchronizer = synchronizer; + } + + /** + * Returns the appropriate reader for the given version. + */ + protected SyncInfoReader getReader(int formatVersion) throws IOException { + switch (formatVersion) { + case 2 : + return new SyncInfoReader_2(workspace, synchronizer); + case 3 : + return new SyncInfoReader_3(workspace, synchronizer); + default : + throw new IOException(NLS.bind(Messages.resources_format, formatVersion)); + } + } + + public void readPartners(DataInputStream input) throws CoreException { + try { + int size = input.readInt(); + Set registry = new HashSet<>(size); + for (int i = 0; i < size; i++) { + String qualifier = input.readUTF(); + String local = input.readUTF(); + registry.add(new QualifiedName(qualifier, local)); + } + synchronizer.setRegistry(registry); + } catch (IOException e) { + String message = NLS.bind(Messages.resources_readSync, e); + throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, message)); + } + } + + public void readSyncInfo(DataInputStream input) throws IOException, CoreException { + // dispatch to the appropriate reader depending + // on the version of the file + int formatVersion = readVersionNumber(input); + SyncInfoReader reader = getReader(formatVersion); + reader.readSyncInfo(input); + } + + protected static int readVersionNumber(DataInputStream input) throws IOException { + return input.readInt(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java new file mode 100644 index 0000000000..105db79654 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.ObjectMap; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * This class is used to read sync info from disk. This is the implementation + * for reading files with version number 2. + */ +public class SyncInfoReader_2 extends SyncInfoReader { + + // for sync info + public static final int INDEX = 1; + public static final int QNAME = 2; + + public SyncInfoReader_2(Workspace workspace, Synchronizer synchronizer) { + super(workspace, synchronizer); + } + + /** + * SAVE_FILE -> VERSION_ID RESOURCE* + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> TYPE BYTES + * TYPE -> INDEX | QNAME + * INDEX -> int int + * QNAME -> int String + * BYTES -> byte[] + * + */ + @Override + public void readSyncInfo(DataInputStream input) throws IOException, CoreException { + try { + List readPartners = new ArrayList<>(5); + while (true) { + IPath path = new Path(input.readUTF()); + readSyncInfo(path, input, readPartners); + } + } catch (EOFException e) { + // ignore end of file + } + } + + private void readSyncInfo(IPath path, DataInputStream input, List readPartners) throws IOException, CoreException { + int size = input.readInt(); + ObjectMap table = new ObjectMap<>(size); + for (int i = 0; i < size; i++) { + QualifiedName name = null; + int type = input.readInt(); + switch (type) { + case QNAME : + String qualifier = input.readUTF(); + String local = input.readUTF(); + name = new QualifiedName(qualifier, local); + readPartners.add(name); + break; + case INDEX : + name = readPartners.get(input.readInt()); + break; + default : + //if we get here then the sync info file is corrupt + String msg = NLS.bind(Messages.resources_readSync, path == null ? "" : path.toString()); //$NON-NLS-1$ + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, path, msg, null); + } + // read the bytes + int length = input.readInt(); + byte[] bytes = new byte[length]; + input.readFully(bytes); + // put them in the table + table.put(name, bytes); + } + // set the table on the resource info + ResourceInfo info = workspace.getResourceInfo(path, true, false); + if (info == null) + return; + info.setSyncInfo(table); + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java new file mode 100644 index 0000000000..263e6303ca --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.ObjectMap; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * This class is used to read sync info from disk. This is the implementation + * for reading files with version number 3. + */ +public class SyncInfoReader_3 extends SyncInfoReader { + + // for sync info + public static final byte INDEX = 1; + public static final byte QNAME = 2; + + public SyncInfoReader_3(Workspace workspace, Synchronizer synchronizer) { + super(workspace, synchronizer); + } + + /** + * SAVE_FILE -> VERSION_ID RESOURCE+ + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> TYPE BYTES + * TYPE -> INDEX | QNAME + * INDEX -> byte int + * QNAME -> byte String + * BYTES -> byte[] + * + */ + @Override + public void readSyncInfo(DataInputStream input) throws IOException, CoreException { + try { + List readPartners = new ArrayList<>(5); + while (true) { + IPath path = new Path(input.readUTF()); + readSyncInfo(path, input, readPartners); + } + } catch (EOFException e) { + // ignore end of file + } + } + + private void readSyncInfo(IPath path, DataInputStream input, List readPartners) throws IOException, CoreException { + int size = input.readInt(); + ObjectMap table = new ObjectMap<>(size); + for (int i = 0; i < size; i++) { + QualifiedName name = null; + byte type = input.readByte(); + switch (type) { + case QNAME : + String qualifier = input.readUTF(); + String local = input.readUTF(); + name = new QualifiedName(qualifier, local); + readPartners.add(name); + break; + case INDEX : + name = readPartners.get(input.readInt()); + break; + default : + //if we get here then the sync info file is corrupt + String msg = NLS.bind(Messages.resources_readSync, path == null ? "" : path.toString()); //$NON-NLS-1$ + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, path, msg, null); + } + // read the bytes + int length = input.readInt(); + byte[] bytes = new byte[length]; + input.readFully(bytes); + // put them in the table + table.put(name, bytes); + } + // set the table on the resource info + ResourceInfo info = workspace.getResourceInfo(path, true, false); + if (info == null) + return; + info.setSyncInfo(table); + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java new file mode 100644 index 0000000000..812f36703f --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import java.io.IOException; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.osgi.util.NLS; + +public class SyncInfoSnapReader { + protected Workspace workspace; + protected Synchronizer synchronizer; + + public SyncInfoSnapReader(Workspace workspace, Synchronizer synchronizer) { + super(); + this.workspace = workspace; + this.synchronizer = synchronizer; + } + + /** + * Returns the appropriate reader for the given version. + */ + protected SyncInfoSnapReader getReader(int formatVersion) throws IOException { + switch (formatVersion) { + case 3 : + return new SyncInfoSnapReader_3(workspace, synchronizer); + default : + throw new IOException(NLS.bind(Messages.resources_format, formatVersion)); + } + } + + public void readSyncInfo(DataInputStream input) throws IOException { + // dispatch to the appropriate reader depending + // on the version of the file + int formatVersion = readVersionNumber(input); + SyncInfoSnapReader reader = getReader(formatVersion); + reader.readSyncInfo(input); + } + + protected static int readVersionNumber(DataInputStream input) throws IOException { + return input.readInt(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java new file mode 100644 index 0000000000..86a7b05f03 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import java.io.IOException; +import org.eclipse.core.internal.utils.ObjectMap; +import org.eclipse.core.runtime.*; + +public class SyncInfoSnapReader_3 extends SyncInfoSnapReader { + + public SyncInfoSnapReader_3(Workspace workspace, Synchronizer synchronizer) { + super(workspace, synchronizer); + } + + private ObjectMap internalReadSyncInfo(DataInputStream input) throws IOException { + int size = input.readInt(); + ObjectMap map = new ObjectMap<>(size); + for (int i = 0; i < size; i++) { + // read the qualified name + String qualifier = input.readUTF(); + String local = input.readUTF(); + QualifiedName name = new QualifiedName(qualifier, local); + // read the bytes + int length = input.readInt(); + byte[] bytes = new byte[length]; + input.readFully(bytes); + // put them in the table + map.put(name, bytes); + } + return map; + } + + /** + * SNAP_FILE -> [VERSION_ID RESOURCE]* + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> QNAME BYTES + * QNAME -> String String + * BYTES -> byte[] + */ + @Override + public void readSyncInfo(DataInputStream input) throws IOException { + IPath path = new Path(input.readUTF()); + ObjectMap map = internalReadSyncInfo(input); + // set the table on the resource info + ResourceInfo info = workspace.getResourceInfo(path, true, false); + if (info == null) + return; + info.setSyncInfo(map); + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java new file mode 100644 index 0000000000..55792833f2 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.resources; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; +import org.eclipse.core.internal.watson.IPathRequestor; +import org.eclipse.core.runtime.QualifiedName; + +public class SyncInfoWriter { + protected Synchronizer synchronizer; + protected Workspace workspace; + + // version number + public static final int SYNCINFO_SAVE_VERSION = 3; + public static final int SYNCINFO_SNAP_VERSION = 3; + + // for sync info + public static final byte INDEX = 1; + public static final byte QNAME = 2; + + public SyncInfoWriter(Workspace workspace, Synchronizer synchronizer) { + super(); + this.workspace = workspace; + this.synchronizer = synchronizer; + } + + public void savePartners(DataOutputStream output) throws IOException { + Set registry = synchronizer.getRegistry(); + output.writeInt(registry.size()); + for (Iterator i = registry.iterator(); i.hasNext();) { + QualifiedName qname = i.next(); + output.writeUTF(qname.getQualifier()); + output.writeUTF(qname.getLocalName()); + } + } + + /** + * SAVE_FILE -> VERSION_ID RESOURCE+ + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> TYPE BYTES + * TYPE -> INDEX | QNAME + * INDEX -> byte int + * QNAME -> byte String + * BYTES -> byte[] + */ + public void saveSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List writtenPartners) throws IOException { + Map table = info.getSyncInfo(false); + if (table == null) + return; + // if this is the first sync info that we have written, then + // write the version id for the file. + if (output.size() == 0) + output.writeInt(SYNCINFO_SAVE_VERSION); + output.writeUTF(requestor.requestPath().toString()); + output.writeInt(table.size()); + for (Map.Entry entry : table.entrySet()) { + QualifiedName name = entry.getKey(); + // if we have already written the partner name once, then write an integer + // constant to represent it instead to remove duplication + int index = writtenPartners.indexOf(name); + if (index == -1) { + // FIXME: what to do about null qualifier? + output.writeByte(QNAME); + output.writeUTF(name.getQualifier()); + output.writeUTF(name.getLocalName()); + writtenPartners.add(name); + } else { + output.writeByte(INDEX); + output.writeInt(index); + } + byte[] bytes = (byte[]) entry.getValue(); + output.writeInt(bytes.length); + output.write(bytes); + } + } + + /** + * SNAP_FILE -> [VERSION_ID RESOURCE]* + * VERSION_ID -> int + * RESOURCE -> RESOURCE_PATH SIZE SYNCINFO* + * RESOURCE_PATH -> String + * SIZE -> int + * SYNCINFO -> QNAME BYTES + * QNAME -> String String + * BYTES -> byte[] + */ + public void snapSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { + if (!info.isSet(ICoreConstants.M_SYNCINFO_SNAP_DIRTY)) + return; + Map table = info.getSyncInfo(false); + if (table == null) + return; + // write the version id for the snapshot. + output.writeInt(SYNCINFO_SNAP_VERSION); + output.writeUTF(requestor.requestPath().toString()); + output.writeInt(table.size()); + for (Map.Entry entry : table.entrySet()) { + QualifiedName name = entry.getKey(); + output.writeUTF(name.getQualifier()); + output.writeUTF(name.getLocalName()); + byte[] bytes = (byte[]) entry.getValue(); + output.writeInt(bytes.length); + output.write(bytes); + } + info.clear(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java new file mode 100644 index 0000000000..008738aee0 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java @@ -0,0 +1,285 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +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.IPathRequestor; +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 Synchronizer implements ISynchronizer { + protected Workspace workspace; + protected SyncInfoWriter writer; + + // Registry of sync partners. Set of qualified names. + protected Set registry = new HashSet<>(5); + + public Synchronizer(Workspace workspace) { + super(); + this.workspace = workspace; + this.writer = new SyncInfoWriter(workspace, this); + } + + /** + * @see ISynchronizer#accept(QualifiedName, IResource, IResourceVisitor, int) + */ + @Override + public void accept(QualifiedName partner, IResource resource, IResourceVisitor visitor, int depth) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(resource != null); + Assert.isLegal(visitor != null); + + // if we don't have sync info for the given identifier, then skip it + if (getSyncInfo(partner, resource) != null) { + // visit the resource and if the visitor says to stop the recursion then return + if (!visitor.visit(resource)) + return; + } + + // adjust depth if necessary + if (depth == IResource.DEPTH_ZERO || resource.getType() == IResource.FILE) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.DEPTH_ZERO; + + // otherwise recurse over the children + IResource[] children = ((IContainer) resource).members(); + for (int i = 0; i < children.length; i++) + accept(partner, children[i], visitor, depth); + } + + /** + * @see ISynchronizer#add(QualifiedName) + */ + @Override + public void add(QualifiedName partner) { + Assert.isLegal(partner != null); + registry.add(partner); + } + + /** + * @see ISynchronizer#flushSyncInfo(QualifiedName, IResource, int) + */ + @Override + public void flushSyncInfo(final QualifiedName partner, final IResource root, final int depth) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(root != null); + + ICoreRunnable body = new ICoreRunnable() { + @Override + public void run(IProgressMonitor monitor) throws CoreException { + IResourceVisitor visitor = new IResourceVisitor() { + @Override + public boolean visit(IResource resource) throws CoreException { + //only need to flush sync info if there is sync info + if (getSyncInfo(partner, resource) != null) + setSyncInfo(partner, resource, null); + return true; + } + }; + root.accept(visitor, depth, true); + } + }; + workspace.run(body, root, IResource.NONE, null); + } + + /** + * @see ISynchronizer#getPartners() + */ + @Override + public QualifiedName[] getPartners() { + return registry.toArray(new QualifiedName[registry.size()]); + } + + /** + * For use by the serialization code. + */ + protected Set getRegistry() { + return registry; + } + + /** + * @see ISynchronizer#getSyncInfo(QualifiedName, IResource) + */ + @Override + public byte[] getSyncInfo(QualifiedName partner, IResource resource) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(resource != null); + + if (!isRegistered(partner)) { + String message = NLS.bind(Messages.synchronizer_partnerNotRegistered, partner); + throw new ResourceException(new ResourceStatus(IResourceStatus.PARTNER_NOT_REGISTERED, message)); + } + + // namespace check, if the resource doesn't exist then return null + ResourceInfo info = workspace.getResourceInfo(resource.getFullPath(), true, false); + return (info == null) ? null : info.getSyncInfo(partner, true); + } + + protected boolean isRegistered(QualifiedName partner) { + Assert.isLegal(partner != null); + return registry.contains(partner); + } + + /** + * @see #savePartners(DataOutputStream) + */ + public void readPartners(DataInputStream input) throws CoreException { + SyncInfoReader reader = new SyncInfoReader(workspace, this); + reader.readPartners(input); + } + + public void restore(IResource resource, IProgressMonitor monitor) throws CoreException { + // first restore from the last save and then apply any snapshots + restoreFromSave(resource); + restoreFromSnap(resource); + } + + protected void restoreFromSave(IResource resource) throws CoreException { + IPath sourceLocation = workspace.getMetaArea().getSyncInfoLocationFor(resource); + IPath tempLocation = workspace.getMetaArea().getBackupLocationFor(sourceLocation); + if (!sourceLocation.toFile().exists() && !tempLocation.toFile().exists()) + return; + try { + DataInputStream input = new DataInputStream(new SafeFileInputStream(sourceLocation.toOSString(), tempLocation.toOSString())); + try { + SyncInfoReader reader = new SyncInfoReader(workspace, this); + reader.readSyncInfo(input); + } 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().getSyncInfoSnapshotLocationFor(resource); + if (!sourceLocation.toFile().exists()) + return; + try { + DataInputStream input = new DataInputStream(new SafeChunkyInputStream(sourceLocation.toFile())); + try { + SyncInfoSnapReader reader = new SyncInfoSnapReader(workspace, this); + while (true) + reader.readSyncInfo(input); + } catch (EOFException eof) { + // ignore end of file -- proceed with what we successfully read + } 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)); + } + } + + /** + * @see ISynchronizer#remove(QualifiedName) + */ + @Override + public void remove(QualifiedName partner) { + Assert.isLegal(partner != null); + if (isRegistered(partner)) { + // remove all sync info for this partner + try { + flushSyncInfo(partner, workspace.getRoot(), IResource.DEPTH_INFINITE); + registry.remove(partner); + } catch (CoreException e) { + // XXX: flush needs to be more resilient and not throw exceptions all the time + Policy.log(e); + } + } + } + + public void savePartners(DataOutputStream output) throws IOException { + writer.savePartners(output); + } + + public void saveSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List writtenPartners) throws IOException { + writer.saveSyncInfo(info, requestor, output, writtenPartners); + } + + protected void setRegistry(Set registry) { + this.registry = registry; + } + + /** + * @see ISynchronizer#setSyncInfo(QualifiedName, IResource, byte[]) + */ + @Override + public void setSyncInfo(QualifiedName partner, IResource resource, byte[] info) throws CoreException { + Assert.isLegal(partner != null); + Assert.isLegal(resource != null); + /* + * We use a dedicated sync rule for this operation and not the resoure + * itself directly. This helps to avoid cancelled auto builds when team provider + * trigger a #setSyncInfo from within a build, e.g. when a builder generates code + * into directories, that are monitored by the team provider. + */ + ISchedulingRule syncRule = workspace.getRuleFactory().syncInfoRule(resource); + try { + workspace.prepareOperation(syncRule, null); + workspace.beginOperation(true); + if (!isRegistered(partner)) { + String message = NLS.bind(Messages.synchronizer_partnerNotRegistered, partner); + throw new ResourceException(new ResourceStatus(IResourceStatus.PARTNER_NOT_REGISTERED, message)); + } + // we do not store sync info on the workspace root + if (resource.getType() == IResource.ROOT) + return; + // if the resource doesn't yet exist then create a phantom so we can set the sync info on it + Resource target = (Resource) resource; + ResourceInfo resourceInfo = workspace.getResourceInfo(target.getFullPath(), true, false); + int flags = target.getFlags(resourceInfo); + if (!target.exists(flags, false)) { + if (info == null) + return; + //ensure it is possible to create this resource + target.checkValidPath(target.getFullPath(), target.getType(), false); + Container parent = (Container) target.getParent(); + parent.checkAccessible(parent.getFlags(parent.getResourceInfo(true, false))); + workspace.createResource(target, true); + } + resourceInfo = target.getResourceInfo(true, true); + resourceInfo.setSyncInfo(partner, info); + resourceInfo.incrementSyncInfoGenerationCount(); + resourceInfo.set(ICoreConstants.M_SYNCINFO_SNAP_DIRTY); + flags = target.getFlags(resourceInfo); + if (target.isPhantom(flags) && resourceInfo.getSyncInfo(false) == null) { + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.resources_deleteProblem, null); + ((Resource) resource).deleteResource(false, status); + if (!status.isOK()) + throw new ResourceException(status); + } + } finally { + workspace.endOperation(syncRule, false); + } + } + + public void snapSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException { + writer.snapSyncInfo(info, requestor, output); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java new file mode 100644 index 0000000000..cc1763b518 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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 java.util.Properties; +import org.eclipse.core.resources.ResourcesPlugin; + +/** + * Provides special internal access to the workspace resource implementation. + * This class is to be used for testing purposes only. + * + * @since 2.0 + */ +public class TestingSupport { + /** + * Returns the save manager's master table. + */ + public static Properties getMasterTable() { + return ((Workspace) ResourcesPlugin.getWorkspace()).getSaveManager().getMasterTable(); + } + + /** + * Blocks the calling thread until background snapshot completes. + * @since 3.0 + */ + public static void waitForSnapshot() { + try { + ((Workspace) ResourcesPlugin.getWorkspace()).getSaveManager().snapshotJob.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + throw new RuntimeException("Interrupted while waiting for snapshot"); //$NON-NLS-1$ + } + } + + /* + * Class cannot be instantiated. + */ + private TestingSupport() { + // not allowed + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java new file mode 100644 index 0000000000..aaa9dbdc12 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VariableDescription.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import org.eclipse.core.runtime.Assert; + +/** + * + */ +public class VariableDescription implements Comparable { + private String name; + private String value; + + public VariableDescription() { + this.name = ""; //$NON-NLS-1$ + this.value = ""; //$NON-NLS-1$ + } + + public VariableDescription(String name, String value) { + super(); + Assert.isNotNull(name); + Assert.isNotNull(value); + this.name = name; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (!(o.getClass() == VariableDescription.class)) + return false; + VariableDescription other = (VariableDescription) o; + return name.equals(other.name) && value == other.value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + @Override + public int hashCode() { + return name.hashCode() + value.hashCode(); + } + + public void setName(String name) { + this.name = name; + } + + public void setValue(String value) { + this.value = value; + } + + /** + * Compare string descriptions in a way that sorts them topologically by + * name. + */ + @Override + public int compareTo(VariableDescription that) { + return name.compareTo(that.name); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java new file mode 100644 index 0000000000..577ad88e35 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileStore.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2009, 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 - Fix for bug 266712 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.InputStream; +import java.net.URI; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.filesystem.provider.FileStore; +import org.eclipse.core.runtime.*; + +/** + * A file store representing a virtual resource. + * A virtual resource always exists and has no children. + */ +public class VirtualFileStore extends FileStore { + private final URI location; + + public VirtualFileStore(URI location) { + this.location = location; + } + + @Override + public String[] childNames(int options, IProgressMonitor monitor) { + return FileStore.EMPTY_STRING_ARRAY; + } + + @Override + public IFileInfo fetchInfo(int options, IProgressMonitor monitor) { + FileInfo result = new FileInfo(); + result.setDirectory(true); + result.setExists(true); + result.setLastModified(1);//last modified of zero indicates non-existence + return result; + } + + @Override + public void delete(int options, IProgressMonitor monitor) { + //nothing to do - virtual resources don't exist in any physical file system + } + + @Override + public IFileStore getChild(String name) { + return EFS.getNullFileSystem().getStore(new Path(name).makeAbsolute()); + } + + @Override + public String getName() { + return "virtual"; //$NON-NLS-1$ + } + + @Override + public IFileStore getParent() { + return null; + } + + @Override + public void move(IFileStore destination, int options, IProgressMonitor monitor) throws CoreException { + destination.mkdir(EFS.NONE, monitor); + } + + @Override + public InputStream openInputStream(int options, IProgressMonitor monitor) { + return null; + } + + @Override + public URI toURI() { + return location; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java new file mode 100644 index 0000000000..500d0249cf --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/VirtualFileSystem.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2009, 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 java.net.URI; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.filesystem.provider.FileSystem; + +/** + * A file system for virtual resources + */ +public class VirtualFileSystem extends FileSystem { + + public VirtualFileSystem() { + super(); + } + + @Override + public IFileStore getStore(URI uri) { + return new VirtualFileStore(uri); + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java new file mode 100644 index 0000000000..2a3d300c83 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java @@ -0,0 +1,325 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +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.IResource; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.*; + +/** + * The work manager governs concurrent access to the workspace tree. The {@link #lock} + * field is used to protect the workspace tree data structure from concurrent + * write attempts. This is an internal lock that is generally not held while + * client code is running. Scheduling rules are used by client code to obtain + * exclusive write access to a portion of the workspace. + * + * This class also tracks operation state for each thread that is involved in an + * operation. This includes prepared and running operation depth, auto-build + * strategy and cancel state. + */ +public class WorkManager implements IManager { + /** + * Scheduling rule for use during resource change notification. This rule + * must always be allowed to nest within a resource rule of any granularity + * since it is used from within the scope of all resource changing + * operations. The purpose of this rule is two-fold: 1. To prevent other + * resource changing jobs from being scheduled while the notification is + * running 2. To cause an exception if a resource change listener tries to + * begin a resource rule during a notification. This also prevents + * deadlock, because the notification thread owns the workspace lock, and + * threads that own the workspace lock must never block trying to acquire a + * resource rule. + */ + class NotifyRule implements ISchedulingRule { + @Override + public boolean contains(ISchedulingRule rule) { + return (rule instanceof IResource) || rule.getClass().equals(NotifyRule.class); + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + return contains(rule); + } + } + + /** + * Indicates that the last checkIn failed, either due to cancelation or due to the + * workspace tree being locked for modifications (during resource change events). + */ + private final ThreadLocal checkInFailed = new ThreadLocal<>(); + /** + * Indicates whether any operations have run that may require a build. + */ + private boolean hasBuildChanges = false; + private IJobManager jobManager; + /** + * The primary workspace lock. This lock must be held by any thread + * modifying the workspace tree. + */ + private final ILock lock; + + /** + * The current depth of running nested operations. + */ + private int nestedOperations = 0; + + private NotifyRule notifyRule = new NotifyRule(); + + private boolean operationCanceled = false; + + /** + * The current depth of prepared operations. + */ + private int preparedOperations = 0; + private Workspace workspace; + + public WorkManager(Workspace workspace) { + this.workspace = workspace; + this.jobManager = Job.getJobManager(); + this.lock = jobManager.newLock(); + } + + /** + * Releases the workspace lock without changing the nested operation depth. + * Must be followed eventually by endUnprotected. Any + * beginUnprotected/endUnprotected pair must be done entirely within the + * scope of a checkIn/checkOut pair. Returns the old lock depth. + * @see #endUnprotected(int) + */ + public int beginUnprotected() { + int depth = lock.getDepth(); + for (int i = 0; i < depth; i++) + lock.release(); + return depth; + } + + /** + * An operation calls this method and it only returns when the operation is + * free to run. + */ + public void checkIn(ISchedulingRule rule, IProgressMonitor monitor) throws CoreException { + boolean success = false; + try { + if (workspace.isTreeLocked()) { + String msg = Messages.resources_cannotModify; + throw new ResourceException(IResourceStatus.WORKSPACE_LOCKED, null, msg, null); + } + jobManager.beginRule(rule, monitor); + lock.acquire(); + incrementPreparedOperations(); + success = true; + } finally { + //remember if we failed to check in, so we can avoid check out + if (!success) + checkInFailed.set(Boolean.TRUE); + } + } + + /** + * Returns true if the check in for this thread failed, in which case the + * check out and other end of operation code should not run. + *

                  + * The failure flag is reset immediately after calling this method. Subsequent + * calls to this method will indicate no failure (unless a new failure has occurred). + * @return true if the checkIn failed, and false otherwise. + */ + public boolean checkInFailed(ISchedulingRule rule) { + if (checkInFailed.get() != null) { + //clear the failure flag for this thread + checkInFailed.set(null); + //must still end the rule even in the case of failure + if (!workspace.isTreeLocked()) + jobManager.endRule(rule); + return true; + } + return false; + } + + /** + * Inform that an operation has finished. + */ + public synchronized void checkOut(ISchedulingRule rule) { + decrementPreparedOperations(); + rebalanceNestedOperations(); + //reset state if this is the end of a top level operation + if (preparedOperations == 0) + hasBuildChanges = false; + //don't let cancelation of this operation affect other operations + operationCanceled = false; + try { + lock.release(); + } finally { + //end rule in finally in case lock.release throws an exception + jobManager.endRule(rule); + } + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + private void decrementPreparedOperations() { + preparedOperations--; + } + + /** + * Re-acquires the workspace lock that was temporarily released during an + * operation, and restores the old lock depth. + * @see #beginUnprotected() + */ + public void endUnprotected(int depth) { + for (int i = 0; i < depth; i++) + lock.acquire(); + } + + /** + * Returns the work manager's lock + */ + ILock getLock() { + return lock; + } + + /** + * Returns the scheduling rule used during resource change notifications. + */ + public ISchedulingRule getNotifyRule() { + return notifyRule; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + public synchronized int getPreparedOperationDepth() { + return preparedOperations; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + void incrementNestedOperations() { + nestedOperations++; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + private void incrementPreparedOperations() { + preparedOperations++; + } + + /** + * Returns true if the nested operation depth is the same as the prepared + * operation depth, and false otherwise. This method can only be safely + * called from inside a workspace operation. Should NOT be called from + * outside a prepareOperation/endOperation block. + */ + boolean isBalanced() { + return nestedOperations == preparedOperations; + } + + /** + * Returns true if the workspace lock has already been acquired by this + * thread, and false otherwise. + */ + public boolean isLockAlreadyAcquired() { + boolean result = false; + try { + boolean success = lock.acquire(0L); + if (success) { + //if lock depth is greater than one, then we already owned it + // before + result = lock.getDepth() > 1; + lock.release(); + } + } catch (InterruptedException e) { + // ignore + } + return result; + } + + /** + * This method can only be safely called from inside a workspace + * operation. Should NOT be called from outside a + * prepareOperation/endOperation block. + */ + public void operationCanceled() { + operationCanceled = true; + } + + /** + * Used to make things stable again after an operation has failed between a + * workspace.prepareOperation() and workspace.beginOperation(). This method + * can only be safely called from inside a workspace operation. Should NOT + * be called from outside a prepareOperation/endOperation block. + */ + public void rebalanceNestedOperations() { + nestedOperations = preparedOperations; + } + + /** + * Indicates if the operation that has just completed may potentially + * require a build. + */ + public void setBuild(boolean hasChanges) { + hasBuildChanges = hasBuildChanges || hasChanges; + } + + /** + * This method can only be safely called from inside a workspace operation. + * Should NOT be called from outside a prepareOperation/endOperation block. + */ + public boolean shouldBuild() { + if (hasBuildChanges) { + if (operationCanceled) + return Policy.buildOnCancel; + return true; + } + return false; + } + + @Override + public void shutdown(IProgressMonitor monitor) { + // do nothing + } + + @Override + public void startup(IProgressMonitor monitor) { + jobManager.beginRule(workspace.getRoot(), monitor); + lock.acquire(); + } + + /** + * This method should be called at the end of the workspace startup, even if the startup failed. + * It must be preceded by a call to startup. It releases the primary workspace lock + * and ends applying the workspace rule to this thread. + */ + void postWorkspaceStartup() { + try { + lock.release(); + } finally { + //end rule in finally in case lock.release throws an exception + jobManager.endRule(workspace.getRoot()); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java new file mode 100644 index 0000000000..85f43b50ed --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java @@ -0,0 +1,2571 @@ +/******************************************************************************* + * 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 + * Red Hat Incorporated - loadProjectDescription(InputStream) + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + * Broadcom Corporation - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.events.*; +import org.eclipse.core.internal.localstore.FileSystemResourceManager; +import org.eclipse.core.internal.properties.IPropertyManager; +import org.eclipse.core.internal.properties.PropertyManager2; +import org.eclipse.core.internal.refresh.RefreshManager; +import org.eclipse.core.internal.resources.ComputeProjectOrder.VertexFilter; +import org.eclipse.core.internal.resources.ComputeProjectOrder.VertexOrder; +import org.eclipse.core.internal.utils.*; +import org.eclipse.core.internal.watson.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.team.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.Bundle; +import org.xml.sax.InputSource; + +/** + * The workspace class is the monolithic nerve center of the resources plugin. + * All interesting functionality stems from this class. + *

                  + *

                  + * The lifecycle of the resources plugin is encapsulated by the {@link #open(IProgressMonitor)} + * and {@link #close(IProgressMonitor)} methods. A closed workspace is completely + * unusable - any attempt to access or modify interesting workspace state on a closed + * workspace will fail. + *

                  + *

                  + * All modifications to the workspace occur within the context of a workspace operation. + * A workspace operation is implemented using the following sequence: + *

                  + * 	try {
                  + *		prepareOperation(...);
                  + *		//check preconditions
                  + *		beginOperation(...);
                  + *		//perform changes
                  + *	} finally {
                  + *		endOperation(...);
                  + *	}
                  + * 
                  + * Workspace operations can be nested arbitrarily. A "top level" workspace operation + * is an operation that is not nested within another workspace operation in the current + * thread. + * See the javadoc of {@link #prepareOperation(ISchedulingRule, IProgressMonitor)}, + * {@link #beginOperation(boolean)}, and {@link #endOperation(ISchedulingRule, boolean)} + * for more details. + *

                  + *

                  + * Major areas of functionality are farmed off to various manager classes. Open a + * type hierarchy on {@link IManager} to see all the different managers. Each + * manager is typically referenced three times in this class: Once in {@link #startup(IProgressMonitor)} + * when it is instantiated, once in {@link #shutdown(IProgressMonitor)} when it + * is destroyed, and once in a manager accessor method. + *

                  + */ +public class Workspace extends PlatformObject implements IWorkspace, ICoreConstants { + public static final boolean caseSensitive = Platform.OS_MACOSX.equals(Platform.getOS()) ? false : new java.io.File("a").compareTo(new java.io.File("A")) != 0; //$NON-NLS-1$ //$NON-NLS-2$ + + /** + * Work manager should never be accessed directly because accessor + * asserts that workspace is still open. + */ + protected WorkManager _workManager; + protected AliasManager aliasManager; + protected BuildManager buildManager; + protected volatile IBuildConfiguration[] buildOrder = null; + protected CharsetManager charsetManager; + protected ContentDescriptionManager contentDescriptionManager; + /** indicates if the workspace crashed in a previous session */ + protected boolean crashed = false; + protected final IWorkspaceRoot defaultRoot = new WorkspaceRoot(Path.ROOT, this); + protected WorkspacePreferences description; + protected FileSystemResourceManager fileSystemManager; + protected final CopyOnWriteArrayList lifecycleListeners = new CopyOnWriteArrayList<>(); + protected LocalMetaArea localMetaArea; + /** + * Helper class for performing validation of resource names and locations. + */ + protected final LocationValidator locationValidator = new LocationValidator(this); + protected MarkerManager markerManager; + /** + * The currently installed Move/Delete hook. + */ + protected IMoveDeleteHook moveDeleteHook = null; + protected NatureManager natureManager; + protected FilterTypeManager filterManager; + protected long nextMarkerId = 0; + protected long nextNodeId = 1; + + protected NotificationManager notificationManager; + protected boolean openFlag = false; + protected ElementTree operationTree; // tree at the start of the current operation + protected PathVariableManager pathVariableManager; + protected IPropertyManager propertyManager; + + protected RefreshManager refreshManager; + + /** + * Scheduling rule factory. This field is null if the factory has not been used + * yet. The accessor method should be used rather than accessing this field + * directly. + */ + private IResourceRuleFactory ruleFactory; + + protected SaveManager saveManager; + /** + * File modification validation. If it is true and validator is null, we try/initialize + * validator first time through. If false, there is no validator. + */ + protected boolean shouldValidate = true; + + /** + * Job that performs periodic string pool canonicalization. + */ + private StringPoolJob stringPoolJob; + + /** + * The synchronizer + */ + protected Synchronizer synchronizer; + + /** + * The currently installed team hook. + */ + protected TeamHook teamHook = null; + + /** + * The workspace tree. The tree is an in-memory representation + * of the resources that make up the workspace. The tree caches + * the structure and state of files and directories on disk (their existence + * and last modified times). When external parties make changes to + * the files on disk, this representation becomes out of sync. A local refresh + * reconciles the state of the files on disk with this tree (@link {@link IResource#refreshLocal(int, IProgressMonitor)}). + * The tree is also used to store metadata associated with resources in + * the workspace (markers, properties, etc). + * + * While the ElementTree data structure can handle both concurrent + * reads and concurrent writes, write access to the tree is governed + * by {@link WorkManager}. + */ + protected volatile ElementTree tree; + + /** + * This field is used to control access to the workspace tree during + * resource change notifications. It tracks which thread, if any, is + * in the middle of a resource change notification. This is used to cause + * attempts to modify the workspace during notifications to fail. + */ + protected volatile Thread treeLocked = null; + + /** + * The currently installed file modification validator. + */ + protected IFileModificationValidator validator = null; + + /** + * Data structure for holding the multi-part outcome of + * IWorkspace.computeProjectBuildConfigOrder. + *

                  + * This class is not intended to be instantiated by clients. + *

                  + * + * @see Workspace#computeProjectBuildConfigOrder(IBuildConfiguration[]) + * @since 3.7 + */ + public static final class ProjectBuildConfigOrder { + /** + * Creates an instance with the given values. + *

                  + * This class is not intended to be instantiated by clients. + *

                  + * + * @param buildConfigurations initial value of buildConfigurations field + * @param hasCycles initial value of hasCycles field + * @param knots initial value of knots field + */ + public ProjectBuildConfigOrder(IBuildConfiguration[] buildConfigurations, boolean hasCycles, IBuildConfiguration[][] knots) { + this.buildConfigurations = buildConfigurations; + this.hasCycles = hasCycles; + this.knots = knots; + } + + /** + * A list of project buildConfigs ordered so as to honor the build configuration reference + * relationships between these project buildConfigs wherever possible. The elements + * are a subset of the ones passed as the buildConfigurations + * parameter to IWorkspace.computeProjectOrder, where + * inaccessible (closed or non-existent) projects have been omitted. + */ + public IBuildConfiguration[] buildConfigurations; + /** + * Indicates whether any of the accessible project buildConfigs in + * buildConfigurations are involved in non-trivial cycles. + * true if the reference graph contains at least + * one cycle involving two or more of the project buildConfigs in + * buildConfigurations, and false if none of the + * project buildConfigs in buildConfigurations are involved in cycles. + */ + 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 accessible project buildConfigs from buildConfigurations that are + * involved in a cycle of mutually dependent references. + */ + public IBuildConfiguration[][] knots; + } + + // Comparator used to provide a stable ordering of project buildConfigs + private static class BuildConfigurationComparator implements Comparator { + public BuildConfigurationComparator() { + } + + @Override + public int compare(IBuildConfiguration px, IBuildConfiguration py) { + int cmp = py.getProject().getName().compareTo(px.getProject().getName()); + if (cmp == 0) + cmp = py.getName().compareTo(px.getName()); + return cmp; + } + } + + /** + * Deletes all the files and directories from the given root down (inclusive). + * Returns false if we could not delete some file or an exception occurred + * at any point in the deletion. + * Even if an exception occurs, a best effort is made to continue deleting. + */ + public static boolean clear(java.io.File root) { + IFileStore fileStore = EFS.getLocalFileSystem().fromLocalFile(root); + try { + fileStore.delete(EFS.NONE, new NullProgressMonitor()); + } catch (CoreException e) { + return false; + } + return true; + } + + public static WorkspaceDescription defaultWorkspaceDescription() { + return new WorkspaceDescription("Workspace"); //$NON-NLS-1$ + } + + /** + * Returns true if the object at the specified position has any + * other copy in the given array. + */ + private static boolean isDuplicate(Object[] array, int position) { + if (array == null || position >= array.length) + return false; + for (int j = position - 1; j >= 0; j--) + if (array[j].equals(array[position])) + return true; + return false; + } + + public Workspace() { + super(); + localMetaArea = new LocalMetaArea(); + tree = new ElementTree(); + /* tree should only be modified during operations */ + tree.immutable(); + treeLocked = Thread.currentThread(); + tree.setTreeData(newElement(IResource.ROOT)); + } + + /** + * Indicates that a build is about to occur. Broadcasts the necessary + * deltas before the build starts. Note that this will cause POST_BUILD + * to be automatically done at the end of the operation in which + * the build occurs. + */ + protected void aboutToBuild(Object source, int trigger) { + //fire a POST_CHANGE first to ensure everyone is up to date before firing PRE_BUILD + broadcastPostChange(); + broadcastBuildEvent(source, IResourceChangeEvent.PRE_BUILD, trigger); + } + + /** + * Adds a listener for internal workspace lifecycle events. There is no way to + * remove lifecycle listeners. + */ + public void addLifecycleListener(ILifecycleListener listener) { + lifecycleListeners.addIfAbsent(listener); + } + + @Override + public void addResourceChangeListener(IResourceChangeListener listener) { + notificationManager.addListener(listener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE); + } + + @Override + public void addResourceChangeListener(IResourceChangeListener listener, int eventMask) { + notificationManager.addListener(listener, eventMask); + } + + /** + * @deprecated Use {@link #addSaveParticipant(String, ISaveParticipant)} instead + */ + @Deprecated + @Override + public ISavedState addSaveParticipant(Plugin plugin, ISaveParticipant participant) throws CoreException { + Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$ + Assert.isNotNull(participant, "Participant must not be null"); //$NON-NLS-1$ + return saveManager.addParticipant(plugin.getBundle().getSymbolicName(), participant); + } + + @Override + public ISavedState addSaveParticipant(String pluginId, ISaveParticipant participant) throws CoreException { + Assert.isNotNull(pluginId, "Plugin id must not be null"); //$NON-NLS-1$ + Assert.isNotNull(participant, "Participant must not be null"); //$NON-NLS-1$ + return saveManager.addParticipant(pluginId, participant); + } + + public void beginOperation(boolean createNewTree) throws CoreException { + WorkManager workManager = getWorkManager(); + workManager.incrementNestedOperations(); + if (!workManager.isBalanced()) + Assert.isTrue(false, "Operation was not prepared."); //$NON-NLS-1$ + if (workManager.getPreparedOperationDepth() > 1) { + if (createNewTree && tree.isImmutable()) + newWorkingTree(); + return; + } + // stash the current tree as the basis for this operation. + operationTree = tree; + if (createNewTree && tree.isImmutable()) + newWorkingTree(); + } + + public void broadcastBuildEvent(Object source, int type, int buildTrigger) { + ResourceChangeEvent event = new ResourceChangeEvent(source, type, buildTrigger, null); + notificationManager.broadcastChanges(tree, event, false); + } + + /** + * Broadcasts an internal workspace lifecycle event to interested + * internal listeners. + */ + protected void broadcastEvent(LifecycleEvent event) throws CoreException { + for (ILifecycleListener listener : lifecycleListeners) + listener.handleEvent(event); + } + + public void broadcastPostChange() { + ResourceChangeEvent event = new ResourceChangeEvent(this, IResourceChangeEvent.POST_CHANGE, 0, null); + notificationManager.broadcastChanges(tree, event, true); + } + + /** + * Add all IBuildConfigurations reachable from config to the configs collection. + * @param configs collection of configurations to extend + * @param config config to find reachable configurations to. + */ + private void recursivelyAddBuildConfigs(Collection/**/ configs, IBuildConfiguration config) { + try { + IBuildConfiguration[] referenced = config.getProject().getReferencedBuildConfigs(config.getName(), false); + for (int i = 0; i < referenced.length; i++) { + if (configs.contains(referenced[i])) + continue; + configs.add(referenced[i]); + recursivelyAddBuildConfigs(configs, referenced[i]); + } + } catch (CoreException e) { + // Not possible, we've checked that the project + configuration are accessible. + Assert.isTrue(false); + } + } + + @Override + public void build(int trigger, IProgressMonitor monitor) throws CoreException { + buildInternal(EMPTY_BUILD_CONFIG_ARRAY, trigger, true, monitor); + } + + @Override + public void build(IBuildConfiguration[] configs, int trigger, boolean buildReferences, IProgressMonitor monitor) throws CoreException { + if (configs.length == 0) + return; + buildInternal(configs, trigger, buildReferences, monitor); + } + + /** + * Build the passed in configurations or the whole workspace. + * @param configs to build or EMPTY_BUILD_CONFIG_ARRAY for the whole workspace + * @param trigger build trigger + * @param buildReferences transitively build referenced build configurations + */ + private void buildInternal(IBuildConfiguration[] configs, int trigger, boolean buildReferences, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + // Bug 343256 use a relaxed scheduling rule if the config we're building uses a relaxed rule. + // Otherwise fall-back to WR. + boolean relaxed = false; + if (Job.getJobManager().currentRule() == null && configs.length > 0) { + relaxed = true; + for (IBuildConfiguration config : configs) { + ISchedulingRule requested = getBuildManager().getRule(config, trigger, null, null); + if (requested != null && requested.contains(getRoot())) { + relaxed = false; + break; + } + } + } + + // PRE + POST_BUILD, and the build itself are allowed to modify resources, so require the current thread's scheduling rule + // to either contain the WR or be null. Therefore, if not null, ensure it contains the WR rule... + final ISchedulingRule buildRule = getRuleFactory().buildRule(); + final ISchedulingRule rule = relaxed ? null : buildRule; + try { + monitor.beginTask("", Policy.opWork); //$NON-NLS-1$ + try { + try { + // Must run the PRE_BUILD with the WRule held before acquiring WS lock + // Can remove this if we run notifications without the WS lock held: bug 249951 + prepareOperation(rule == null ? buildRule : rule, monitor); + beginOperation(true); + aboutToBuild(this, trigger); + } finally { + if (rule == null) { + endOperation(buildRule, false); + prepareOperation(rule, monitor); + beginOperation(false); + } + } + IStatus result; + try { + + // Calculate the build-order having called the pre-build notification (which may change build order) + // If configs == EMPTY_BUILD_CONFIG_ARRAY => This is a full workspace build. + IBuildConfiguration[] requestedConfigs = configs; + if (configs == EMPTY_BUILD_CONFIG_ARRAY) { + if (trigger != IncrementalProjectBuilder.CLEAN_BUILD) + configs = getBuildOrder(); + else { + // clean all accessible configurations + List configArr = new ArrayList<>(); + IProject[] prjs = getRoot().getProjects(); + for (int i = 0; i < prjs.length; i++) + if (prjs[i].isAccessible()) + configArr.addAll(Arrays.asList(prjs[i].getBuildConfigs())); + configs = configArr.toArray(new IBuildConfiguration[configArr.size()]); + } + } else { + // Order the passed in build configurations + resolve references if requested + Set refsList = new HashSet<>(); + for (int i = 0; i < configs.length; i++) { + // Check project + build configuration are accessible. + if (!configs[i].getProject().isAccessible() || !configs[i].getProject().hasBuildConfig(configs[i].getName())) + continue; + refsList.add(configs[i]); + // Find transitive closure of referenced project buildConfigs + if (buildReferences) + recursivelyAddBuildConfigs(refsList, configs[i]); + } + + // Order the referenced project buildConfigs + ProjectBuildConfigOrder order = computeProjectBuildConfigOrder(refsList.toArray(new IBuildConfiguration[refsList.size()])); + configs = order.buildConfigurations; + } + + result = getBuildManager().build(configs, requestedConfigs, trigger, Policy.subMonitorFor(monitor, Policy.opWork)); + } finally { + // Run the POST_BUILD with the WRule held + if (rule == null) { + endOperation(rule, false); + prepareOperation(buildRule, monitor); + beginOperation(false); + } + //must fire POST_BUILD if PRE_BUILD has occurred + broadcastBuildEvent(this, IResourceChangeEvent.POST_BUILD, trigger); + } + if (!result.isOK()) + throw new ResourceException(result); + } finally { + //building may close the tree, but we are still inside an operation so open it + if (tree.isImmutable()) + newWorkingTree(); + // Rule will be the build-rule from the POST_BUILD refresh + endOperation(buildRule, false); + } + } finally { + monitor.done(); + } + } + + /** + * Returns whether creating executable extensions is acceptable + * at this point in time. In particular, returns false + * when the system bundle is shutting down, which only occurs + * when the entire framework is exiting. + */ + private boolean canCreateExtensions() { + return Platform.getBundle("org.eclipse.osgi").getState() != Bundle.STOPPING; //$NON-NLS-1$ + } + + @Override + public void checkpoint(boolean build) { + try { + final ISchedulingRule rule = getWorkManager().getNotifyRule(); + try { + prepareOperation(rule, null); + beginOperation(true); + broadcastPostChange(); + } finally { + endOperation(rule, build); + } + } catch (CoreException e) { + Policy.log(e.getStatus().getSeverity(), e.getMessage(), e); + } + } + + /** + * Closes this workspace; ignored if this workspace is not open. + * The state of this workspace is not saved before the workspace + * is shut down. + *

                  + * If the workspace was saved immediately prior to closing, + * it will have the same set of projects + * (open or closed) when reopened for a subsequent session. + * Otherwise, closing a workspace may lose some or all of the + * changes made since the last save or snapshot. + *

                  + *

                  + * Note that session properties are discarded when a workspace is closed. + *

                  + *

                  + * 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 and cancellation are not desired + * @exception CoreException if the workspace could not be shutdown. + */ + public void close(IProgressMonitor monitor) throws CoreException { + //nothing to do if the workspace failed to open + if (!isOpen()) + return; + try { + String msg = Messages.resources_closing_0; + SubMonitor subMonitor = SubMonitor.convert(monitor, msg, 20); + subMonitor.subTask(msg); + //this operation will never end because the world is going away + try { + stringPoolJob.cancel(); + //shutdown save manager now so a last snapshot can be taken before we close + //note: you can't call #save() from within a nested operation + saveManager.shutdown(null); + saveManager.reportSnapshotRequestor(); + prepareOperation(getRoot(), subMonitor.newChild(1)); + //shutdown notification first to avoid calling third parties during shutdown + notificationManager.shutdown(null); + beginOperation(true); + IProject[] projects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + subMonitor.setWorkRemaining(projects.length + 2); + for (int i = 0; i < projects.length; i++) { + //notify managers of closing so they can cleanup + broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, projects[i])); + subMonitor.worked(1); + } + //empty the workspace tree so we leave in a clean state + deleteResource(getRoot()); + openFlag = false; + // endOperation not needed here + } finally { + // Shutdown needs to be executed regardless of failures + shutdown(subMonitor.newChild(2, SubMonitor.SUPPRESS_SUBTASK)); + } + } finally { + //release the scheduling rule to be a good job citizen + Job.getJobManager().endRule(getRoot()); + } + } + + /** + * Computes the global total ordering of all open projects in the + * workspace based on project references. If an existing and open project P + * references another existing and open project Q also included in the list, + * then Q should come before P in the resulting ordering. Closed and non- + * existent projects are ignored, and will not appear in the result. References + * to non-existent or closed projects are also ignored, as are any self- + * references. + *

                  + * When there are choices, the choice is made in a reasonably stable way. For + * example, given an arbitrary choice between two projects, the one with the + * lower collating project name is usually selected. + *

                  + *

                  + * When the project reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references P1, + * P4 references P3, and P2 and P3 reference each other, then exactly one of the + * relationships between P2 and P3 will have to be ignored. The outcome will be + * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains + * complete details of any cycles present. + *

                  + * + * @return result describing the global project order + * @since 2.1 + */ + private VertexOrder computeFullProjectOrder() { + // determine the full set of accessible projects in the workspace + // order the set in descending alphabetical order of project name + SortedSet allAccessibleProjects = new TreeSet<>(new Comparator() { + @Override + public int compare(IProject px, IProject py) { + return py.getName().compareTo(px.getName()); + } + }); + IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + // List edges + List edges = new ArrayList<>(allProjects.length); + for (int i = 0; i < allProjects.length; i++) { + Project project = (Project) allProjects[i]; + // ignore projects that are not accessible + if (!project.isAccessible()) + continue; + ProjectDescription desc = project.internalGetDescription(); + if (desc == null) + continue; + //obtain both static and dynamic project references + IProject[] refs = desc.getAllReferences(false); + allAccessibleProjects.add(project); + for (int j = 0; j < refs.length; j++) { + IProject ref = refs[j]; + // ignore self references and references to projects that are not accessible + if (ref.isAccessible() && !ref.equals(project)) + edges.add(new IProject[] {project, ref}); + } + } + return ComputeProjectOrder.computeVertexOrder(allAccessibleProjects, edges); + } + + /** + * Computes the global total ordering of all open projects' active buildConfigs in the + * workspace based on build configuration references. If an existing and open project's build config P + * references another existing and open project's build config Q, then Q should come before P + * in the resulting ordering. If a build config references a non-active build config it is + * added to the resulting ordered list. Closed and non-existent projects/buildConfigs are + * ignored, and will not appear in the result. References to non-existent or closed + * projects/buildConfigs are also ignored, as are any self-references. + *

                  + * When there are choices, the choice is made in a reasonably stable way. For + * example, given an arbitrary choice between two project buildConfigs, the one with the + * lower collating project name and build config name will appear earlier in the list. + *

                  + *

                  + * When the build configuration reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references P1, + * P4 references P3, and P2 and P3 reference each other, then exactly one of the + * relationships between P2 and P3 will have to be ignored. The outcome will be + * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains + * complete details of any cycles present. + *

                  + * + * @return result describing the global active build configuration order + */ + private VertexOrder computeActiveBuildConfigOrder() { + // Determine the full set of accessible active project buildConfigs in the workspace, + // and all the accessible project buildConfigs that they reference. This forms a set + // of all the project buildConfigs that will be returned. + // Order the set in descending alphabetical order of project name then build config name, + // as a secondary sort applied after sorting based on references, to achieve a stable + // ordering. + SortedSet allAccessibleBuildConfigs = new TreeSet<>(new BuildConfigurationComparator()); + + // For each project's active build config, perform a depth first search in the reference graph + // rooted at that build config. + // This generates the required subset of the reference graph that is required to order all + // the dependencies of the active project buildConfigs. + IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List edges = new ArrayList<>(allProjects.length); + + for (int i = 0; i < allProjects.length; i++) { + Project project = (Project) allProjects[i]; + // Ignore projects that are not accessible + if (!project.isAccessible()) + continue; + + // If the active build configuration hasn't already been explored + // perform a depth first search rooted at it + if (!allAccessibleBuildConfigs.contains(project.internalGetActiveBuildConfig())) { + allAccessibleBuildConfigs.add(project.internalGetActiveBuildConfig()); + Stack stack = new Stack<>(); + stack.push(project.internalGetActiveBuildConfig()); + + while (!stack.isEmpty()) { + IBuildConfiguration buildConfiguration = stack.pop(); + + // Add all referenced buildConfigs from the current configuration + // (it is guaranteed to be accessible as it was pushed onto the stack) + Project subProject = (Project) buildConfiguration.getProject(); + IBuildConfiguration[] refs = subProject.internalGetReferencedBuildConfigs(buildConfiguration.getName(), false); + for (int j = 0; j < refs.length; j++) { + IBuildConfiguration ref = refs[j]; + + // Ignore self references and references to projects that are not accessible + if (ref.equals(buildConfiguration)) + continue; + + // Add the referenced accessible configuration + edges.add(new IBuildConfiguration[] {buildConfiguration, ref}); + + // If we have already explored the referenced configuration, don't explore it again + if (allAccessibleBuildConfigs.contains(ref)) + continue; + + allAccessibleBuildConfigs.add(ref); + + // Push the referenced configuration onto the stack so that it is explored by the depth first search + stack.push(ref); + } + } + } + } + return ComputeProjectOrder.computeVertexOrder(allAccessibleBuildConfigs, edges); + } + + /** + * Computes the global total ordering of all project buildConfigs in the workspace based + * on build config references. If an existing and open build config P + * references another existing and open project build config Q, then Q should come before P + * in the resulting ordering. Closed and non-existent projects/buildConfigs are + * ignored, and will not appear in the result. References to non-existent or closed + * projects/buildConfigs are also ignored, as are any self-references. + *

                  + * When there are choices, the choice is made in a reasonably stable way. For + * example, given an arbitrary choice between two project buildConfigs, the one with the + * lower collating project name and build config name will appear earlier in the list. + *

                  + *

                  + * When the build config reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references P1, + * P4 references P3, and P2 and P3 reference each other, then exactly one of the + * relationships between P2 and P3 will have to be ignored. The outcome will be + * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains + * complete details of any cycles present. + *

                  + * + * @return result describing the global project build configuration order + */ + private VertexOrder computeFullBuildConfigOrder() { + // Compute the order for all accessible project buildConfigs + SortedSet allAccessibleBuildConfigurations = new TreeSet<>(new BuildConfigurationComparator()); + + IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + List edges = new ArrayList<>(allProjects.length); + + for (int i = 0; i < allProjects.length; i++) { + Project project = (Project) allProjects[i]; + // Ignore projects that are not accessible + if (!project.isAccessible()) + continue; + + IBuildConfiguration[] configs = project.internalGetBuildConfigs(false); + for (int j = 0; j < configs.length; j++) { + IBuildConfiguration config = configs[j]; + allAccessibleBuildConfigurations.add(config); + IBuildConfiguration[] refs = project.internalGetReferencedBuildConfigs(config.getName(), false); + for (int k = 0; k < refs.length; k++) { + IBuildConfiguration ref = refs[k]; + + // Ignore self references + if (ref.equals(config)) + continue; + + // Add the reference to the set of reachable configs + add an edge + allAccessibleBuildConfigurations.add(ref); + edges.add(new IBuildConfiguration[] {config, ref}); + } + } + } + return ComputeProjectOrder.computeVertexOrder(allAccessibleBuildConfigurations, edges); + } + + private static ProjectOrder vertexOrderToProjectOrder(VertexOrder order) { + IProject[] projects = new IProject[order.vertexes.length]; + System.arraycopy(order.vertexes, 0, projects, 0, order.vertexes.length); + IProject[][] knots = new IProject[order.knots.length][]; + for (int i = 0; i < order.knots.length; i++) { + knots[i] = new IProject[order.knots[i].length]; + System.arraycopy(order.knots[i], 0, knots[i], 0, order.knots[i].length); + } + return new ProjectOrder(projects, order.hasCycles, knots); + } + + private static ProjectBuildConfigOrder vertexOrderToProjectBuildConfigOrder(VertexOrder order) { + IBuildConfiguration[] buildConfigs = new IBuildConfiguration[order.vertexes.length]; + System.arraycopy(order.vertexes, 0, buildConfigs, 0, order.vertexes.length); + IBuildConfiguration[][] knots = new IBuildConfiguration[order.knots.length][]; + for (int i = 0; i < order.knots.length; i++) { + knots[i] = new IBuildConfiguration[order.knots[i].length]; + System.arraycopy(order.knots[i], 0, knots[i], 0, order.knots[i].length); + } + return new ProjectBuildConfigOrder(buildConfigs, order.hasCycles, knots); + } + + @Deprecated + @Override + public IProject[][] computePrerequisiteOrder(IProject[] targets) { + return computePrerequisiteOrder1(targets); + } + + /* + * Compatible reimplementation of + * IWorkspace.computePrerequisiteOrder using + * IWorkspace.computeProjectOrder. + * + * @since 2.1 + */ + private IProject[][] computePrerequisiteOrder1(IProject[] projects) { + IWorkspace.ProjectOrder r = computeProjectOrder(projects); + if (!r.hasCycles) { + return new IProject[][] {r.projects, new IProject[0]}; + } + // when there are cycles, we need to remove all knotted projects from + // r.projects to form result[0] and merge all knots to form result[1] + // Set bad + Set bad = new HashSet<>(); + // Set bad + Set keepers = new HashSet<>(Arrays.asList(r.projects)); + for (int i = 0; i < r.knots.length; i++) { + IProject[] knot = r.knots[i]; + for (int j = 0; j < knot.length; j++) { + IProject project = knot[j]; + // keep only selected projects in knot + if (keepers.contains(project)) { + bad.add(project); + } + } + } + IProject[] result2 = new IProject[bad.size()]; + bad.toArray(result2); + // List p + List p = new LinkedList<>(); + p.addAll(Arrays.asList(r.projects)); + for (Iterator it = p.listIterator(); it.hasNext();) { + IProject project = it.next(); + if (bad.contains(project)) { + // remove knotted projects from the main answer + it.remove(); + } + } + IProject[] result1 = new IProject[p.size()]; + p.toArray(result1); + return new IProject[][] {result1, result2}; + } + + @Override + public ProjectOrder computeProjectOrder(IProject[] projects) { + // Compute the full project order for all accessible projects + VertexOrder fullProjectOrder = computeFullProjectOrder(); + + // Create a filter to remove all projects that are not in the list asked for + final Set projectSet = new HashSet<>(projects.length); + projectSet.addAll(Arrays.asList(projects)); + VertexFilter filter = new VertexFilter() { + @Override + public boolean matches(Object vertex) { + return !projectSet.contains(vertex); + } + }; + + // Filter the order and return it + return vertexOrderToProjectOrder(ComputeProjectOrder.filterVertexOrder(fullProjectOrder, filter)); + } + + /** + * Computes a total ordering of the given projects buildConfigs based on both static and + * dynamic project references. If an existing and open project's build configuratioin P references + * another existing and open project's configuration Q also included in the list, then Q + * should come before P in the resulting ordering. Closed and non-existent + * projects are ignored, and will not appear in the result. References to + * non-existent or closed projects/buildConfigs are also ignored, as are any + * self-references. The total ordering is always consistent with the global + * total ordering of all open projects' buildConfigs in the workspace. + *

                  + * When there are choices, the choice is made in a reasonably stable way. + * For example, given an arbitrary choice between two project buildConfigs, the one with + * the lower collating configuration name is usually selected. + *

                  + *

                  + * When the project reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references + * P1, P4 references P3, and P2 and P3 reference each other, then exactly + * one of the relationships between P2 and P3 will have to be ignored. The + * outcome will be either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result + * also contains complete details of any cycles present. + *

                  + *

                  + * This method is time-consuming and should not be called unnecessarily. + * There are a very limited set of changes to a workspace that could affect + * the outcome: creating, renaming, or deleting a project; opening or + * closing a project; deleting a build configuration; adding or removing a build configuration reference. + *

                  + * + * @param buildConfigs the build configurations to order + * @return result describing the build configuration order + * @since 3.7 + */ + public ProjectBuildConfigOrder computeProjectBuildConfigOrder(IBuildConfiguration[] buildConfigs) { + // Compute the full project order for all accessible projects + VertexOrder fullBuildConfigOrder = computeFullBuildConfigOrder(); + + // Create a filter to remove all project buildConfigs that are not in the list asked for + final Set projectConfigSet = new HashSet<>(buildConfigs.length); + projectConfigSet.addAll(Arrays.asList(buildConfigs)); + VertexFilter filter = new VertexFilter() { + @Override + public boolean matches(Object vertex) { + return !projectConfigSet.contains(vertex); + } + }; + + // Filter the order and return it + return vertexOrderToProjectBuildConfigOrder(ComputeProjectOrder.filterVertexOrder(fullBuildConfigOrder, filter)); + } + + @Override + public IStatus copy(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + return copy(resources, destination, updateFlags, monitor); + } + + @Override + public IStatus copy(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + Assert.isLegal(resources != null); + int opWork = Math.max(resources.length, 1); + int totalWork = Policy.totalWork * opWork / Policy.opWork; + String message = Messages.resources_copying_0; + monitor.beginTask(message, totalWork); + if (resources.length == 0) + return Status.OK_STATUS; + // to avoid concurrent changes to this array + resources = resources.clone(); + IPath parentPath = null; + message = Messages.resources_copyProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + try { + prepareOperation(getRoot(), monitor); + beginOperation(true); + for (int i = 0; i < resources.length; i++) { + Policy.checkCanceled(monitor); + IResource resource = resources[i]; + if (resource == null || isDuplicate(resources, i)) { + monitor.worked(1); + continue; + } + // test siblings + if (parentPath == null) + parentPath = resource.getFullPath().removeLastSegments(1); + if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) { + // test copy requirements + try { + IPath destinationPath = destination.append(resource.getName()); + IStatus requirements = ((Resource) resource).checkCopyRequirements(destinationPath, resource.getType(), updateFlags); + if (requirements.isOK()) { + try { + resource.copy(destinationPath, updateFlags, Policy.subMonitorFor(monitor, 1)); + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + status.merge(requirements); + } + } catch (CoreException e) { + monitor.worked(1); + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + message = NLS.bind(Messages.resources_notChild, resources[i].getFullPath(), parentPath); + status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resources[i].getFullPath(), message)); + } + } + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } finally { + endOperation(getRoot(), true); + } + if (status.matches(IStatus.ERROR)) + throw new ResourceException(status); + return status.isOK() ? Status.OK_STATUS : (IStatus) status; + } finally { + monitor.done(); + } + } + + protected void copyTree(IResource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo) throws CoreException { + copyTree(source, destination, depth, updateFlags, keepSyncInfo, false, source.getType() == IResource.PROJECT); + } + + private void copyTree(IResource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo, boolean moveResources, boolean movingProject) throws CoreException { + + // retrieve the resource at the destination if there is one (phantoms included). + // if there isn't one, then create a new handle based on the type that we are + // trying to copy + IResource destinationResource = getRoot().findMember(destination, true); + int destinationType; + if (destinationResource == null) { + if (source.getType() == IResource.FILE) + destinationType = IResource.FILE; + else if (destination.segmentCount() == 1) + destinationType = IResource.PROJECT; + else + destinationType = IResource.FOLDER; + destinationResource = newResource(destination, destinationType); + } else + destinationType = destinationResource.getType(); + + // create the resource at the destination + ResourceInfo sourceInfo = ((Resource) source).getResourceInfo(true, false); + if (destinationType != source.getType()) { + sourceInfo = (ResourceInfo) sourceInfo.clone(); + sourceInfo.setType(destinationType); + } + ResourceInfo newInfo = createResource(destinationResource, sourceInfo, false, true, keepSyncInfo); + // get/set the node id from the source's resource info so we can later put it in the + // info for the destination resource. This will help us generate the proper deltas, + // indicating a move rather than a add/delete + newInfo.setNodeId(sourceInfo.getNodeId()); + + // preserve local sync info but not location info + newInfo.setFlags(newInfo.getFlags() | (sourceInfo.getFlags() & M_LOCAL_EXISTS)); + newInfo.setFileStoreRoot(null); + + // forget content-related caching flags + newInfo.clear(M_CONTENT_CACHE); + + // update link locations in project descriptions + if (source.isLinked()) { + LinkDescription linkDescription; + URI sourceLocationURI = transferVariableDefinition(source, destinationResource, source.getLocationURI()); + if (((updateFlags & IResource.SHALLOW) != 0) || ((Resource) source).isUnderVirtual()) { + //for shallow move the destination is a linked resource with the same location + newInfo.set(ICoreConstants.M_LINK); + linkDescription = new LinkDescription(destinationResource, sourceLocationURI); + } else { + //for deep move the destination is not a linked resource + newInfo.clear(ICoreConstants.M_LINK); + linkDescription = null; + } + if (moveResources && !movingProject) { + if (((Project) source.getProject()).internalGetDescription().setLinkLocation(source.getProjectRelativePath(), null)) + ((Project) source.getProject()).writeDescription(updateFlags); + } + Project project = (Project) destinationResource.getProject(); + project.internalGetDescription().setLinkLocation(destinationResource.getProjectRelativePath(), linkDescription); + project.writeDescription(updateFlags); + newInfo.setFileStoreRoot(null); + } + + // update filters in project descriptions + if (source.getProject().exists() && source instanceof Container && ((Container) source).hasFilters()) { + Project sourceProject = (Project) source.getProject(); + LinkedList originalDescriptions = sourceProject.internalGetDescription().getFilter(source.getProjectRelativePath()); + LinkedList filterDescriptions = FilterDescription.copy(originalDescriptions, destinationResource); + if (moveResources && !movingProject) { + if (((Project) source.getProject()).internalGetDescription().setFilters(source.getProjectRelativePath(), null)) + ((Project) source.getProject()).writeDescription(updateFlags); + } + Project project = (Project) destinationResource.getProject(); + project.internalGetDescription().setFilters(destinationResource.getProjectRelativePath(), filterDescriptions); + project.writeDescription(updateFlags); + } + + // do the recursion. if we have a file then it has no members so return. otherwise + // recursively call this method on the container's members if the depth tells us to + if (depth == IResource.DEPTH_ZERO || source.getType() == IResource.FILE) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.DEPTH_ZERO; + //copy .project file first if project is being copied, otherwise links won't be able to update description + boolean projectCopy = source.getType() == IResource.PROJECT && destinationType == IResource.PROJECT; + if (projectCopy) { + IResource dotProject = ((Project) source).findMember(IProjectDescription.DESCRIPTION_FILE_NAME); + if (dotProject != null) + copyTree(dotProject, destination.append(dotProject.getName()), depth, updateFlags, keepSyncInfo, moveResources, movingProject); + } + IResource[] children = ((IContainer) source).members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); + for (int i = 0, imax = children.length; i < imax; i++) { + String childName = children[i].getName(); + if (!projectCopy || !childName.equals(IProjectDescription.DESCRIPTION_FILE_NAME)) { + IPath childPath = destination.append(childName); + copyTree(children[i], childPath, depth, updateFlags, keepSyncInfo, moveResources, movingProject); + } + } + } + + public URI transferVariableDefinition(IResource source, IResource dest, URI sourceURI) throws CoreException { + IPath srcLoc = source.getLocation(); + IPath srcRawLoc = source.getRawLocation(); + if ((srcLoc != null) && (srcRawLoc != null) && !srcLoc.equals(srcRawLoc)) { + // the location is variable relative + if (!source.getProject().equals(dest.getProject())) { + String variable = srcRawLoc.segment(0); + variable = copyVariable(source, dest, variable); + IPath newLocation = Path.fromPortableString(variable).append(srcRawLoc.removeFirstSegments(1)); + sourceURI = toURI(newLocation); + } else { + sourceURI = toURI(srcRawLoc); + } + } + return sourceURI; + } + + URI toURI(IPath path) { + if (path.isAbsolute()) + return org.eclipse.core.filesystem.URIUtil.toURI(path); + try { + return new URI(null, null, path.toPortableString(), null); + } catch (URISyntaxException e) { + return org.eclipse.core.filesystem.URIUtil.toURI(path); + } + } + + String copyVariable(IResource source, IResource dest, String variable) throws CoreException { + IPathVariableManager destPathVariableManager = dest.getPathVariableManager(); + IPathVariableManager srcPathVariableManager = source.getPathVariableManager(); + + IPath srcValue = URIUtil.toPath(srcPathVariableManager.getURIValue(variable)); + if (srcValue == null) // if the variable doesn't exist, return another + // variable that doesn't exist either + return PathVariableUtil.getUniqueVariableName(variable, dest); + IPath resolvedSrcValue = URIUtil.toPath(srcPathVariableManager.resolveURI(URIUtil.toURI(srcValue))); + + boolean variableExisted = false; + // look if the exact same variable exists + if (destPathVariableManager.isDefined(variable)) { + variableExisted = true; + IPath destValue = URIUtil.toPath(destPathVariableManager.getURIValue(variable)); + if (destValue != null && URIUtil.toPath(destPathVariableManager.resolveURI(URIUtil.toURI(destValue))).equals(resolvedSrcValue)) + return variable; + } + // look if one variable in the destination project matches + String[] variables = destPathVariableManager.getPathVariableNames(); + for (int i = 0; i < variables.length; i++) { + if (!PathVariableUtil.isPreferred(variables[i])) + continue; + IPath resolveDestVariable = URIUtil.toPath(destPathVariableManager.resolveURI(destPathVariableManager.getURIValue(variables[i]))); + if (resolveDestVariable != null && resolveDestVariable.equals(resolvedSrcValue)) { + return variables[i]; + } + } + // if the variable doesn't exist in the dest project, or + // if the value is different than the source project, we have to create + // an equivalent. + String destVariable = PathVariableUtil.getUniqueVariableName(variable, dest); + + boolean shouldConvertToRelative = true; + if (!srcValue.equals(resolvedSrcValue) && !variableExisted) { + // the variable content contains references to more variables + + String[] referencedVariables = PathVariableUtil.splitVariableNames(srcValue.toPortableString()); + shouldConvertToRelative = false; + // If the variable value is of type ${PARENT-COUNT-VAR}, + // we can avoid generating an intermediate variable and convert it directly. + if (referencedVariables.length == 1) { + if (PathVariableUtil.isParentVariable(referencedVariables[0])) + shouldConvertToRelative = true; + } + + if (!shouldConvertToRelative) { + String[] segments = PathVariableUtil.splitVariablesAndContent(srcValue.toPortableString()); + StringBuilder result = new StringBuilder(); + for (int i = 0; i < segments.length; i++) { + String var = PathVariableUtil.extractVariable(segments[i]); + if (var.length() > 0) { + String copiedVariable = copyVariable(source, dest, var); + int index = segments[i].indexOf(var); + if (index != -1) { + result.append(segments[i].substring(0, index)); + result.append(copiedVariable); + int start = index + var.length(); + int end = segments[i].length(); + result.append(segments[i].substring(start, end)); + } + } else + result.append(segments[i]); + } + srcValue = Path.fromPortableString(result.toString()); + } + } + if (shouldConvertToRelative) { + IPath relativeSrcValue = PathVariableUtil.convertToPathRelativeMacro(destPathVariableManager, resolvedSrcValue, dest, true, null); + if (relativeSrcValue != null) + srcValue = relativeSrcValue; + } + destPathVariableManager.setURIValue(destVariable, URIUtil.toURI(srcValue)); + return destVariable; + } + + /** + * Returns the number of resources in a subtree of the resource tree. + * + * @param root The subtree to count resources for + * @param depth The depth of the subtree to count + * @param phantom If true, phantoms are included, otherwise they are ignored. + */ + public int countResources(IPath root, int depth, final boolean phantom) { + if (!tree.includes(root)) + return 0; + switch (depth) { + case IResource.DEPTH_ZERO : + return 1; + case IResource.DEPTH_ONE : + return 1 + tree.getChildCount(root); + case IResource.DEPTH_INFINITE : + final int[] count = new int[1]; + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree aTree, IPathRequestor requestor, Object elementContents) { + if (phantom || !((ResourceInfo) elementContents).isSet(M_PHANTOM)) + count[0]++; + return true; + } + }; + new ElementTreeIterator(tree, root).iterate(visitor); + return count[0]; + } + return 0; + } + + /* + * Creates the given resource in the tree and returns the new resource info object. + * If phantom is true, the created element is marked as a phantom. + * If there is already be an element in the tree for the given resource + * in the given state (i.e., phantom), a CoreException is thrown. + * If there is already a phantom in the tree and the phantom flag is false, + * the element is overwritten with the new element. (but the synchronization + * information is preserved) + */ + public ResourceInfo createResource(IResource resource, boolean phantom) throws CoreException { + return createResource(resource, null, phantom, false, false); + } + + /** + * Creates a resource, honoring update flags requesting that the resource + * be immediately made derived, hidden and/or team private + */ + public ResourceInfo createResource(IResource resource, int updateFlags) throws CoreException { + ResourceInfo info = createResource(resource, null, false, false, false); + if ((updateFlags & IResource.DERIVED) != 0) + info.set(M_DERIVED); + if ((updateFlags & IResource.TEAM_PRIVATE) != 0) + info.set(M_TEAM_PRIVATE_MEMBER); + if ((updateFlags & IResource.HIDDEN) != 0) + info.set(M_HIDDEN); + // if ((updateFlags & IResource.VIRTUAL) != 0) + // info.set(M_VIRTUAL); + return info; + } + + /* + * Creates the given resource in the tree and returns the new resource info object. + * If phantom is true, the created element is marked as a phantom. + * If there is already be an element in the tree for the given resource + * in the given state (i.e., phantom), a CoreException is thrown. + * If there is already a phantom in the tree and the phantom flag is false, + * the element is overwritten with the new element. (but the synchronization + * information is preserved) If the specified resource info is null, then create + * a new one. + * + * If keepSyncInfo is set to be true, the sync info in the given ResourceInfo is NOT + * cleared before being created and thus any sync info already existing at that namespace + * (as indicated by an already existing phantom resource) will be lost. + */ + public ResourceInfo createResource(IResource resource, ResourceInfo info, boolean phantom, boolean overwrite, boolean keepSyncInfo) throws CoreException { + info = info == null ? newElement(resource.getType()) : (ResourceInfo) info.clone(); + ResourceInfo original = getResourceInfo(resource.getFullPath(), true, false); + if (phantom) { + info.set(M_PHANTOM); + info.clearModificationStamp(); + } + // if nothing existed at the destination then just create the resource in the tree + if (original == null) { + // we got here from a copy/move. we don't want to copy over any sync info + // from the source so clear it. + if (!keepSyncInfo) + info.setSyncInfo(null); + tree.createElement(resource.getFullPath(), info); + } else { + // if overwrite==true then slam the new info into the tree even if one existed before + if (overwrite || (!phantom && original.isSet(M_PHANTOM))) { + // copy over the sync info and flags from the old resource info + // since we are replacing a phantom with a real resource + // DO NOT set the sync info dirty flag because we want to + // preserve the old sync info so its not dirty + // XXX: must copy over the generic sync info from the old info to the new + // XXX: do we really need to clone the sync info here? + if (!keepSyncInfo) + info.setSyncInfo(original.getSyncInfo(true)); + // mark the markers bit as dirty so we snapshot an empty marker set for + // the new resource + info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY); + tree.setElementData(resource.getFullPath(), info); + } else { + String message = NLS.bind(Messages.resources_mustNotExist, resource.getFullPath()); + throw new ResourceException(IResourceStatus.RESOURCE_EXISTS, resource.getFullPath(), message, null); + } + } + return info; + } + + @Override + public IStatus delete(IResource[] resources, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= IResource.KEEP_HISTORY; + return delete(resources, updateFlags, monitor); + } + + @Override + public IStatus delete(IResource[] resources, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + int opWork = Math.max(resources.length, 1); + int totalWork = Policy.totalWork * opWork / Policy.opWork; + String message = Messages.resources_deleting_0; + monitor.beginTask(message, totalWork); + message = Messages.resources_deleteProblem; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + if (resources.length == 0) + return result; + resources = resources.clone(); // to avoid concurrent changes to this array + try { + prepareOperation(getRoot(), monitor); + beginOperation(true); + for (int i = 0; i < resources.length; i++) { + Policy.checkCanceled(monitor); + Resource resource = (Resource) resources[i]; + if (resource == null) { + monitor.worked(1); + continue; + } + try { + resource.delete(updateFlags, Policy.subMonitorFor(monitor, 1)); + } catch (CoreException e) { + // Don't really care about the exception unless the resource is still around. + ResourceInfo info = resource.getResourceInfo(false, false); + if (resource.exists(resource.getFlags(info), false)) { + message = NLS.bind(Messages.resources_couldnotDelete, resource.getFullPath()); + result.merge(new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, resource.getFullPath(), message)); + result.merge(e.getStatus()); + } + } + } + if (result.matches(IStatus.ERROR)) + throw new ResourceException(result); + return result; + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } finally { + endOperation(getRoot(), true); + } + } finally { + monitor.done(); + } + } + + @Override + public void deleteMarkers(IMarker[] markers) throws CoreException { + Assert.isNotNull(markers); + if (markers.length == 0) + return; + // clone to avoid outside changes + markers = markers.clone(); + try { + prepareOperation(null, null); + beginOperation(true); + for (int i = 0; i < markers.length; ++i) + if (markers[i] != null && markers[i].getResource() != null) + markerManager.removeMarker(markers[i].getResource(), markers[i].getId()); + } finally { + endOperation(null, false); + } + } + + /** + * Delete the given resource from the current tree of the receiver. + * This method simply removes the resource from the tree. No cleanup or + * other management is done. Use IResource.delete for proper deletion. + * If the given resource is the root, all of its children (i.e., all projects) are + * deleted but the root is left. + */ + void deleteResource(IResource resource) { + IPath path = resource.getFullPath(); + if (path.equals(Path.ROOT)) { + IProject[] children = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < children.length; i++) + tree.deleteElement(children[i].getFullPath()); + } else + tree.deleteElement(path); + } + + /** + * End an operation (group of resource changes). + * Notify interested parties that resource changes have taken place. All + * registered resource change listeners are notified. If autobuilding is + * enabled, a build is run. + */ + public void endOperation(ISchedulingRule rule, boolean build) throws CoreException { + WorkManager workManager = getWorkManager(); + //don't do any end operation work if we failed to check in + if (workManager.checkInFailed(rule)) + return; + // This is done in a try finally to ensure that we always decrement the operation count + // and release the workspace lock. This must be done at the end because snapshot + // and "hasChanges" comparison have to happen without interference from other threads. + boolean hasTreeChanges = false; + boolean depthOne = false; + try { + workManager.setBuild(build); + // if we are not exiting a top level operation then just decrement the count and return + depthOne = workManager.getPreparedOperationDepth() == 1; + if (!(notificationManager.shouldNotify() || depthOne)) { + notificationManager.requestNotify(); + return; + } + // do the following in a try/finally to ensure that the operation tree is nulled at the end + // as we are completing a top level operation. + try { + notificationManager.beginNotify(); + // check for a programming error on using beginOperation/endOperation + Assert.isTrue(workManager.getPreparedOperationDepth() > 0, "Mismatched begin/endOperation"); //$NON-NLS-1$ + + // At this time we need to re-balance the nested operations. It is necessary because + // build() and snapshot() should not fail if they are called. + workManager.rebalanceNestedOperations(); + + //find out if any operation has potentially modified the tree + hasTreeChanges = workManager.shouldBuild(); + //double check if the tree has actually changed + if (hasTreeChanges) + hasTreeChanges = operationTree != null && ElementTree.hasChanges(tree, operationTree, ResourceComparator.getBuildComparator(), true); + broadcastPostChange(); + // Request a snapshot if we are sufficiently out of date. + saveManager.snapshotIfNeeded(hasTreeChanges); + } finally { + // make sure the tree is immutable if we are ending a top-level operation. + if (depthOne) { + tree.immutable(); + operationTree = null; + } else + newWorkingTree(); + } + } finally { + workManager.checkOut(rule); + } + if (depthOne) + buildManager.endTopLevel(hasTreeChanges); + } + + /** + * Flush the build order cache for the workspace. The buildOrder cache contains the total + * order of the build configurations in the workspace, including projects not mentioned in + * the workspace description. + */ + protected void flushBuildOrder() { + buildOrder = null; + } + + @Override + public void forgetSavedTree(String pluginId) { + saveManager.forgetSavedTree(pluginId); + } + + public AliasManager getAliasManager() { + return aliasManager; + } + + /** + * Returns this workspace's build manager + */ + public BuildManager getBuildManager() { + return buildManager; + } + + /** + * Returns the order in which open projects in this workspace will be built. + * The result returned is a list of project buildConfigs, that need to be built + * in order to successfully build the active config of every project in this + * workspace. + *

                  + * The build configuration order is based on information specified in the workspace + * description. The project build configs are built in the order specified by + * IWorkspaceDescription.getBuildOrder; closed or non-existent + * projects are ignored and not included in the result. If any open projects are + * not specified in this order, they are appended to the end of the build order + * sorted by project name (to provide a stable ordering). + *

                  + *

                  + * If IWorkspaceDescription.getBuildOrder is non-null, the default + * build order is used (calculated based on references); again, only open projects' + * buildConfigs are included in the result. + *

                  + *

                  + * The returned value is cached in the buildOrder field. + *

                  + * + * @return the list of currently open projects active buildConfigs (and the project buildConfigs + * they depend on) in the workspace in the order in which they would be built by IWorkspace.build. + * @see IWorkspace#build(int, IProgressMonitor) + * @see IWorkspaceDescription#getBuildOrder() + */ + public IBuildConfiguration[] getBuildOrder() { + // Return the build order cache. + if (buildOrder != null) + return buildOrder; + + // see if a particular build order is specified + String[] order = description.getBuildOrder(false); + if (order != null) { + LinkedHashSet configs = new LinkedHashSet<>(); + + // convert from project names to active project buildConfigs + // and eliminate non-existent and closed projects + for (int i = 0; i < order.length; i++) { + IProject project = getRoot().getProject(order[i]); + if (project.isAccessible()) + configs.add(((Project) project).internalGetActiveBuildConfig()); + } + + // Add projects not mentioned in the build order to the end, in a sensible reference order + configs.addAll(Arrays.asList(vertexOrderToProjectBuildConfigOrder(computeActiveBuildConfigOrder()).buildConfigurations)); + + // Update the cache - Java 5 volatile memory barrier semantics + IBuildConfiguration[] bo = new IBuildConfiguration[configs.size()]; + configs.toArray(bo); + this.buildOrder = bo; + } else + // use default project build order + // computed for all accessible projects in workspace + buildOrder = vertexOrderToProjectBuildConfigOrder(computeActiveBuildConfigOrder()).buildConfigurations; + + return buildOrder; + } + + public CharsetManager getCharsetManager() { + return charsetManager; + } + + public ContentDescriptionManager getContentDescriptionManager() { + return contentDescriptionManager; + } + + @Override + public Map getDanglingReferences() { + IProject[] projects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + Map result = new HashMap<>(projects.length); + for (int i = 0; i < projects.length; i++) { + Project project = (Project) projects[i]; + if (!project.isAccessible()) + continue; + IProject[] refs = project.internalGetDescription().getReferencedProjects(false); + List dangling = new ArrayList<>(refs.length); + for (int j = 0; j < refs.length; j++) + if (!refs[i].exists()) + dangling.add(refs[i]); + if (!dangling.isEmpty()) + result.put(projects[i], dangling.toArray(new IProject[dangling.size()])); + } + return result; + } + + @Override + public IWorkspaceDescription getDescription() { + WorkspaceDescription workingCopy = defaultWorkspaceDescription(); + description.copyTo(workingCopy); + return workingCopy; + } + + /** + * Returns the current element tree for this workspace + */ + public ElementTree getElementTree() { + return tree; + } + + public FileSystemResourceManager getFileSystemManager() { + return fileSystemManager; + } + + /** + * Returns the marker manager for this workspace + */ + public MarkerManager getMarkerManager() { + return markerManager; + } + + public LocalMetaArea getMetaArea() { + return localMetaArea; + } + + protected IMoveDeleteHook getMoveDeleteHook() { + if (moveDeleteHook == null) + initializeMoveDeleteHook(); + return moveDeleteHook; + } + + @Override + public IFilterMatcherDescriptor getFilterMatcherDescriptor(String filterMatcherId) { + return filterManager.getFilterDescriptor(filterMatcherId); + } + + @Override + public IFilterMatcherDescriptor[] getFilterMatcherDescriptors() { + return filterManager.getFilterDescriptors(); + } + + @Override + public IProjectNatureDescriptor getNatureDescriptor(String natureId) { + return natureManager.getNatureDescriptor(natureId); + } + + @Override + public IProjectNatureDescriptor[] getNatureDescriptors() { + return natureManager.getNatureDescriptors(); + } + + /** + * Returns the nature manager for this workspace. + */ + public NatureManager getNatureManager() { + return natureManager; + } + + public NotificationManager getNotificationManager() { + return notificationManager; + } + + @Override + public IPathVariableManager getPathVariableManager() { + return pathVariableManager; + } + + public IPropertyManager getPropertyManager() { + return propertyManager; + } + + /** + * Returns the refresh manager for this workspace + */ + public RefreshManager getRefreshManager() { + return refreshManager; + } + + /** + * Returns the resource info for the identified resource. + * null is returned if no such resource can be found. + * If the phantom flag is true, phantom resources are considered. + * If the mutable flag is true, the info is opened for change. + * + * This method DOES NOT throw an exception if the resource is not found. + */ + public ResourceInfo getResourceInfo(IPath path, boolean phantom, boolean mutable) { + try { + if (path.segmentCount() == 0) { + ResourceInfo info = (ResourceInfo) tree.getTreeData(); + Assert.isNotNull(info, "Tree root info must never be null"); //$NON-NLS-1$ + return info; + } + ResourceInfo result = null; + if (!tree.includes(path)) + return null; + if (mutable) + result = (ResourceInfo) tree.openElementData(path); + else + result = (ResourceInfo) tree.getElementData(path); + if (result != null && (!phantom && result.isSet(M_PHANTOM))) + return null; + return result; + } catch (IllegalArgumentException e) { + return null; + } + } + + @Override + public IWorkspaceRoot getRoot() { + return defaultRoot; + } + + @Override + public IResourceRuleFactory getRuleFactory() { + //note that the rule factory is created lazily because it + //requires loading the teamHook extension + if (ruleFactory == null) + ruleFactory = new Rules(this); + return ruleFactory; + } + + public SaveManager getSaveManager() { + return saveManager; + } + + @Override + public ISynchronizer getSynchronizer() { + return synchronizer; + } + + /** + * Returns the installed team hook. Never returns null. + */ + protected TeamHook getTeamHook() { + if (teamHook == null) + initializeTeamHook(); + return teamHook; + } + + /** + * We should not have direct references to this field. All references should go through + * this method. + */ + public WorkManager getWorkManager() throws CoreException { + if (_workManager == null) { + String message = Messages.resources_shutdown; + throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, null, message)); + } + return _workManager; + } + + /** + * A move/delete hook hasn't been initialized. Check the extension point and + * try to create a new hook if a user has one defined as an extension. Otherwise + * use the Core's implementation as the default. + */ + protected void initializeMoveDeleteHook() { + try { + if (!canCreateExtensions()) + return; + IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MOVE_DELETE_HOOK); + // no-one is plugged into the extension point so disable validation + if (configs == null || configs.length == 0) { + return; + } + // can only have one defined at a time. log a warning + if (configs.length > 1) { + //XXX: should provide a meaningful status code + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneHook, null); + Policy.log(status); + return; + } + // otherwise we have exactly one hook extension. Try to create a new instance + // from the user-specified class. + try { + IConfigurationElement config = configs[0]; + moveDeleteHook = (IMoveDeleteHook) config.createExecutableExtension("class"); //$NON-NLS-1$ + } catch (CoreException e) { + //ignore the failure if we are shutting down (expected since extension + //provider plugin has probably already shut down + if (canCreateExtensions()) { + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initHook, e); + Policy.log(status); + } + } + } finally { + // for now just use Core's implementation + if (moveDeleteHook == null) + moveDeleteHook = new MoveDeleteHook(); + } + } + + /** + * A team hook hasn't been initialized. Check the extension point and + * try to create a new hook if a user has one defined as an extension. + * Otherwise use the Core's implementation as the default. + */ + protected void initializeTeamHook() { + try { + if (!canCreateExtensions()) + return; + IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_TEAM_HOOK); + // no-one is plugged into the extension point so disable validation + if (configs == null || configs.length == 0) { + return; + } + // can only have one defined at a time. log a warning + if (configs.length > 1) { + //XXX: should provide a meaningful status code + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneTeamHook, null); + Policy.log(status); + return; + } + // otherwise we have exactly one hook extension. Try to create a new instance + // from the user-specified class. + try { + IConfigurationElement config = configs[0]; + teamHook = (TeamHook) config.createExecutableExtension("class"); //$NON-NLS-1$ + } catch (CoreException e) { + //ignore the failure if we are shutting down (expected since extension + //provider plugin has probably already shut down + if (canCreateExtensions()) { + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initTeamHook, e); + Policy.log(status); + } + } + } finally { + // default to use Core's implementation + //create anonymous subclass because TeamHook is abstract + if (teamHook == null) + teamHook = new TeamHook() { + // empty + }; + } + } + + /** + * A file modification validator hasn't been initialized. Check the extension point and + * try to create a new validator if a user has one defined as an extension. + */ + protected void initializeValidator() { + shouldValidate = false; + if (!canCreateExtensions()) + return; + IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_FILE_MODIFICATION_VALIDATOR); + // no-one is plugged into the extension point so disable validation + if (configs == null || configs.length == 0) { + return; + } + // can only have one defined at a time. log a warning, disable validation, but continue with + // the #setContents (e.g. don't throw an exception) + if (configs.length > 1) { + //XXX: should provide a meaningful status code + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneValidator, null); + Policy.log(status); + return; + } + // otherwise we have exactly one validator extension. Try to create a new instance + // from the user-specified class. + try { + IConfigurationElement config = configs[0]; + validator = (IFileModificationValidator) config.createExecutableExtension("class"); //$NON-NLS-1$ + shouldValidate = true; + } catch (CoreException e) { + //ignore the failure if we are shutting down (expected since extension + //provider plugin has probably already shut down + if (canCreateExtensions()) { + IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initValidator, e); + Policy.log(status); + } + } + } + + public WorkspaceDescription internalGetDescription() { + return description; + } + + @Override + public boolean isAutoBuilding() { + return description.isAutoBuilding(); + } + + public boolean isOpen() { + return openFlag; + } + + @Override + public boolean isTreeLocked() { + return treeLocked == Thread.currentThread(); + } + + /** + * Link the given tree into the receiver's tree at the specified resource. + */ + protected void linkTrees(IPath path, ElementTree[] newTrees) { + tree = tree.mergeDeltaChain(path, newTrees); + } + + @Override + public IProjectDescription loadProjectDescription(InputStream stream) throws CoreException { + IProjectDescription result = null; + result = new ProjectDescriptionReader().read(new InputSource(stream)); + if (result == null) { + String message = NLS.bind(Messages.resources_errorReadProject, stream.toString()); + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message, null); + throw new ResourceException(status); + } + return result; + } + + @Override + public IProjectDescription loadProjectDescription(IPath path) throws CoreException { + IProjectDescription result = null; + IOException e = null; + try { + result = new ProjectDescriptionReader().read(path); + if (result != null) { + // check to see if we are using in the default area or not. use java.io.File for + // testing equality because it knows better w.r.t. drives and case sensitivity + IPath user = path.removeLastSegments(1); + IPath platform = getRoot().getLocation().append(result.getName()); + if (!user.toFile().equals(platform.toFile())) + result.setLocation(user); + } + } catch (IOException ex) { + e = ex; + } + if (result == null || e != null) { + String message = NLS.bind(Messages.resources_errorReadProject, path.toOSString()); + IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message, e); + throw new ResourceException(status); + } + return result; + } + + @Override + public IStatus move(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= IResource.KEEP_HISTORY; + return move(resources, destination, updateFlags, monitor); + } + + @Override + public IStatus move(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + Assert.isLegal(resources != null); + int opWork = Math.max(resources.length, 1); + int totalWork = Policy.totalWork * opWork / Policy.opWork; + String message = Messages.resources_moving_0; + monitor.beginTask(message, totalWork); + if (resources.length == 0) + return Status.OK_STATUS; + resources = resources.clone(); // to avoid concurrent changes to this array + IPath parentPath = null; + message = Messages.resources_moveProblem; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + try { + prepareOperation(getRoot(), monitor); + beginOperation(true); + for (int i = 0; i < resources.length; i++) { + Policy.checkCanceled(monitor); + Resource resource = (Resource) resources[i]; + if (resource == null || isDuplicate(resources, i)) { + monitor.worked(1); + continue; + } + // test siblings + if (parentPath == null) + parentPath = resource.getFullPath().removeLastSegments(1); + if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) { + // test move requirements + try { + IStatus requirements = resource.checkMoveRequirements(destination.append(resource.getName()), resource.getType(), updateFlags); + if (requirements.isOK()) { + try { + resource.move(destination.append(resource.getName()), updateFlags, Policy.subMonitorFor(monitor, 1)); + } catch (CoreException e) { + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + status.merge(requirements); + } + } catch (CoreException e) { + monitor.worked(1); + status.merge(e.getStatus()); + } + } else { + monitor.worked(1); + message = NLS.bind(Messages.resources_notChild, resource.getFullPath(), parentPath); + status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resource.getFullPath(), message)); + } + } + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } finally { + endOperation(getRoot(), true); + } + if (status.matches(IStatus.ERROR)) + throw new ResourceException(status); + return status.isOK() ? (IStatus) Status.OK_STATUS : (IStatus) status; + } finally { + monitor.done(); + } + } + + /** + * Moves this resource's subtree to the destination. This operation should only be + * used by move methods. Destination must be a valid destination for this resource. + * The keepSyncInfo boolean is used to indicated whether or not the sync info should + * be moved from the source to the destination. + */ + + /* package */ + void move(Resource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo) throws CoreException { + // overlay the tree at the destination path, preserving any important info + // in any already existing resource information + copyTree(source, destination, depth, updateFlags, keepSyncInfo, true, source.getType() == IResource.PROJECT); + source.fixupAfterMoveSource(); + } + + /** + * Create and return a new tree element of the given type. + */ + protected ResourceInfo newElement(int type) { + ResourceInfo result = null; + switch (type) { + case IResource.FILE : + case IResource.FOLDER : + result = new ResourceInfo(); + break; + case IResource.PROJECT : + result = new ProjectInfo(); + break; + case IResource.ROOT : + result = new RootInfo(); + break; + } + result.setNodeId(nextNodeId()); + updateModificationStamp(result); + result.setType(type); + return result; + } + + @Override + public IBuildConfiguration newBuildConfig(String projectName, String configName) { + return new BuildConfiguration(getRoot().getProject(projectName), configName); + } + + @Override + public IProjectDescription newProjectDescription(String projectName) { + IProjectDescription result = new ProjectDescription(); + result.setName(projectName); + return result; + } + + public Resource newResource(IPath path, int type) { + String message; + switch (type) { + case IResource.FOLDER : + if (path.segmentCount() < ICoreConstants.MINIMUM_FOLDER_SEGMENT_LENGTH) { + message = "Path must include project and resource name: " + path.toString(); //$NON-NLS-1$ + Assert.isLegal(false, message); + } + return new Folder(path.makeAbsolute(), this); + case IResource.FILE : + if (path.segmentCount() < ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH) { + message = "Path must include project and resource name: " + path.toString(); //$NON-NLS-1$ + Assert.isLegal(false, message); + } + return new File(path.makeAbsolute(), this); + case IResource.PROJECT : + return (Resource) getRoot().getProject(path.lastSegment()); + case IResource.ROOT : + return (Resource) getRoot(); + } + Assert.isLegal(false); + // will never get here because of assertion. + return null; + } + + /** + * Opens a new mutable element tree layer, thus allowing + * modifications to the tree. + */ + public ElementTree newWorkingTree() { + tree = tree.newEmptyDelta(); + return tree; + } + + /** + * Returns the next, previously unassigned, marker id. + */ + protected long nextMarkerId() { + return nextMarkerId++; + } + + protected long nextNodeId() { + return nextNodeId++; + } + + /** + * Opens this workspace using the data at its location in the local file system. + * This workspace must not be open. + * If the operation succeeds, the result will detail any serious + * (but non-fatal) problems encountered while opening the workspace. + * The status code will be OK if there were no problems. + * An exception is thrown if there are fatal problems opening the workspace, + * in which case the workspace is left closed. + *

                  + * 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 and cancellation are not desired + * @return status with code OK if no problems; + * otherwise status describing any serious but non-fatal problems. + * + * @exception CoreException if the workspace could not be opened. + * Reasons include: + *
                    + *
                  • There is no valid workspace structure at the given location + * in the local file system.
                  • + *
                  • The workspace structure on disk appears to be hopelessly corrupt.
                  • + *
                  + * @see ResourcesPlugin#getWorkspace() + */ + public IStatus open(IProgressMonitor monitor) throws CoreException { + // This method is not inside an operation because it is the one responsible for + // creating the WorkManager object (who takes care of operations). + String message = Messages.resources_workspaceOpen; + Assert.isTrue(!isOpen(), message); + if (!getMetaArea().hasSavedWorkspace()) { + message = Messages.resources_readWorkspaceMeta; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, Platform.getLocation(), message, null); + } + description = new WorkspacePreferences(); + + // if we have an old description file, read it (getting rid of it) + WorkspaceDescription oldDescription = getMetaArea().readOldWorkspace(); + if (oldDescription != null) { + description.copyFrom(oldDescription); + ResourcesPlugin.getPlugin().savePluginPreferences(); + } + + // create root location + localMetaArea.locationFor(getRoot()).toFile().mkdirs(); + + IProgressMonitor nullMonitor = Policy.monitorFor(null); + startup(nullMonitor); + //restart the notification manager so it is initialized with the right tree + notificationManager.startup(null); + openFlag = true; + if (crashed || refreshRequested()) { + try { + refreshManager.refresh(getRoot()); + } catch (RuntimeException e) { + //don't fail entire open if refresh failed, just report as warning + return new ResourceStatus(IResourceStatus.INTERNAL_ERROR, Path.ROOT, Messages.resources_errorMultiRefresh, e); + } + } + //finally register a string pool participant + stringPoolJob = new StringPoolJob(); + stringPoolJob.addStringPoolParticipant(saveManager, getRoot()); + return Status.OK_STATUS; + } + + /** + * Called before checking the pre-conditions of an operation. Optionally supply + * a scheduling rule to determine when the operation is safe to run. If a scheduling + * rule is supplied, this method will block until it is safe to run. + * + * @param rule the scheduling rule that describes what this operation intends to modify. + */ + public void prepareOperation(ISchedulingRule rule, IProgressMonitor monitor) throws CoreException { + try { + //make sure autobuild is not running if it conflicts with this operation + ISchedulingRule buildRule = getRuleFactory().buildRule(); + if (rule != null && buildRule != null && (rule.isConflicting(buildRule) || buildRule.isConflicting(rule))) + buildManager.interrupt(); + } finally { + getWorkManager().checkIn(rule, monitor); + } + if (!isOpen()) { + String message = Messages.resources_workspaceClosed; + throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, null); + } + } + + protected boolean refreshRequested() { + String[] args = Platform.getCommandLineArgs(); + for (int i = 0; i < args.length; i++) + if (args[i].equalsIgnoreCase(REFRESH_ON_STARTUP)) + return true; + return false; + } + + @Override + public void removeResourceChangeListener(IResourceChangeListener listener) { + notificationManager.removeListener(listener); + } + + @Deprecated + @Override + public void removeSaveParticipant(Plugin plugin) { + Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$ + saveManager.removeParticipant(plugin.getBundle().getSymbolicName()); + } + + @Override + public void removeSaveParticipant(String pluginId) { + Assert.isNotNull(pluginId, "Plugin id must not be null"); //$NON-NLS-1$ + saveManager.removeParticipant(pluginId); + } + + @Override + public void run(ICoreRunnable action, IProgressMonitor monitor) throws CoreException { + run(action, defaultRoot, IWorkspace.AVOID_UPDATE, monitor); + } + + @Override + public void run(ICoreRunnable action, ISchedulingRule rule, int options, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ + int depth = -1; + boolean avoidNotification = (options & IWorkspace.AVOID_UPDATE) != 0; + try { + prepareOperation(rule, monitor); + beginOperation(true); + if (avoidNotification) + avoidNotification = notificationManager.beginAvoidNotify(); + depth = getWorkManager().beginUnprotected(); + action.run(Policy.subMonitorFor(monitor, Policy.opWork)); + } catch (OperationCanceledException e) { + getWorkManager().operationCanceled(); + throw e; + } catch (CoreException e) { + if (e.getStatus().getSeverity() == IStatus.CANCEL) + getWorkManager().operationCanceled(); + throw e; + } finally { + if (avoidNotification) + notificationManager.endAvoidNotify(); + if (depth >= 0) + getWorkManager().endUnprotected(depth); + endOperation(rule, false); + } + } finally { + monitor.done(); + } + } + + @Override + public void run(IWorkspaceRunnable action, IProgressMonitor monitor) throws CoreException { + run((ICoreRunnable) action, defaultRoot, IWorkspace.AVOID_UPDATE, monitor); + } + + @Override + public void run(IWorkspaceRunnable action, ISchedulingRule rule, int options, IProgressMonitor monitor) throws CoreException { + run((ICoreRunnable) action, rule, options, monitor); + } + + @Override + public IStatus save(boolean full, IProgressMonitor monitor) throws CoreException { + return this.save(full, false, monitor); + } + + public IStatus save(boolean full, boolean keepConsistencyWhenCanceled, IProgressMonitor monitor) throws CoreException { + String message; + if (full) { + //according to spec it is illegal to start a full save inside another operation + if (getWorkManager().isLockAlreadyAcquired()) { + message = Messages.resources_saveOp; + throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, new IllegalStateException()); + } + return saveManager.save(ISaveContext.FULL_SAVE, keepConsistencyWhenCanceled, null, monitor); + } + // A snapshot was requested. Start an operation (if not already started) and + // signal that a snapshot should be done at the end. + try { + prepareOperation(getRoot(), monitor); + beginOperation(false); + saveManager.requestSnapshot(); + message = Messages.resources_snapRequest; + return new ResourceStatus(IStatus.OK, message); + } finally { + endOperation(getRoot(), false); + } + } + + public void setCrashed(boolean value) { + crashed = value; + if (crashed) { + String msg = "The workspace exited with unsaved changes in the previous session; refreshing workspace to recover changes."; //$NON-NLS-1$ + Policy.log(new ResourceStatus(ICoreConstants.CRASH_DETECTED, msg)); + if (Policy.DEBUG) + Policy.debug(msg); + } + } + + @Override + public void setDescription(IWorkspaceDescription value) { + // if both the old and new description's build orders are null, leave the + // workspace's build order slot because it is caching the computed order. + // Otherwise, set the slot to null to force recomputing or building from the description. + WorkspaceDescription newDescription = (WorkspaceDescription) value; + String[] newOrder = newDescription.getBuildOrder(false); + if (description.getBuildOrder(false) != null || newOrder != null) + buildOrder = null; + description.copyFrom(newDescription); + ResourcesPlugin.getPlugin().savePluginPreferences(); + } + + public void setTreeLocked(boolean locked) { + Assert.isTrue(!locked || treeLocked == null, "The workspace tree is already locked"); //$NON-NLS-1$ + treeLocked = locked ? Thread.currentThread() : null; + } + + /** + * Shuts down the workspace managers. + */ + protected void shutdown(IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + IManager[] managers = {buildManager, propertyManager, pathVariableManager, charsetManager, fileSystemManager, markerManager, _workManager, aliasManager, refreshManager, contentDescriptionManager, natureManager, filterManager}; + monitor.beginTask("", managers.length); //$NON-NLS-1$ + String message = Messages.resources_shutdownProblems; + MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); + // best effort to shutdown every object and free resources + for (int i = 0; i < managers.length; i++) { + IManager manager = managers[i]; + if (manager == null) + monitor.worked(1); + else { + try { + manager.shutdown(Policy.subMonitorFor(monitor, 1)); + } catch (Exception e) { + message = Messages.resources_shutdownProblems; + status.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, e)); + } + } + } + buildManager = null; + notificationManager = null; + propertyManager = null; + pathVariableManager = null; + fileSystemManager = null; + markerManager = null; + synchronizer = null; + saveManager = null; + _workManager = null; + aliasManager = null; + refreshManager = null; + charsetManager = null; + contentDescriptionManager = null; + if (!status.isOK()) + throw new CoreException(status); + } finally { + monitor.done(); + } + } + + @Override + public String[] sortNatureSet(String[] natureIds) { + return natureManager.sortNatureSet(natureIds); + } + + /** + * Starts all the workspace manager classes. + */ + protected void startup(IProgressMonitor monitor) throws CoreException { + // ensure the tree is locked during the startup notification + try { + _workManager = new WorkManager(this); + _workManager.startup(null); + fileSystemManager = new FileSystemResourceManager(this); + fileSystemManager.startup(monitor); + pathVariableManager = new PathVariableManager(); + pathVariableManager.startup(null); + natureManager = new NatureManager(); + natureManager.startup(null); + filterManager = new FilterTypeManager(); + filterManager.startup(null); + buildManager = new BuildManager(this, getWorkManager().getLock()); + buildManager.startup(null); + notificationManager = new NotificationManager(this); + notificationManager.startup(null); + markerManager = new MarkerManager(this); + markerManager.startup(null); + synchronizer = new Synchronizer(this); + saveManager = new SaveManager(this); + saveManager.startup(null); + propertyManager = new PropertyManager2((Workspace) ResourcesPlugin.getWorkspace()); + propertyManager.startup(monitor); + charsetManager = new CharsetManager(this); + charsetManager.startup(null); + contentDescriptionManager = new ContentDescriptionManager(); + contentDescriptionManager.startup(null); + //must start after save manager, because (read) access to tree is needed + //must start after other managers to avoid potential cyclic dependency on uninitialized managers (see bug 316182) + //must start before alias manager (see bug 94829) + refreshManager = new RefreshManager(this); + refreshManager.startup(null); + //must start at the end to avoid potential cyclic dependency on other uninitialized managers (see bug 369177) + aliasManager = new AliasManager(this); + aliasManager.startup(null); + } finally { + //unlock tree even in case of failure, otherwise shutdown will also fail + treeLocked = null; + _workManager.postWorkspaceStartup(); + } + } + + /** + * Returns a string representation of this working state's + * structure suitable for debug purposes. + */ + public String toDebugString() { + final StringBuilder buffer = new StringBuilder("\nDump of " + toString() + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$ + buffer.append(" parent: " + tree.getParent()); //$NON-NLS-1$ + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree aTree, IPathRequestor requestor, Object elementContents) { + buffer.append("\n " + requestor.requestPath() + ": " + elementContents); //$NON-NLS-1$ //$NON-NLS-2$ + return true; + } + }; + new ElementTreeIterator(tree, Path.ROOT).iterate(visitor); + return buffer.toString(); + } + + public void updateModificationStamp(ResourceInfo info) { + info.incrementModificationStamp(); + } + + @Override + public IStatus validateEdit(final IFile[] files, final Object context) { + // if validation is turned off then just return + if (!shouldValidate) { + String message = Messages.resources_readOnly2; + MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.READ_ONLY_LOCAL, message, null); + for (int i = 0; i < files.length; i++) { + if (files[i].isReadOnly()) { + IPath filePath = files[i].getFullPath(); + message = NLS.bind(Messages.resources_readOnly, filePath); + result.add(new ResourceStatus(IResourceStatus.READ_ONLY_LOCAL, filePath, message)); + } + } + return result.getChildren().length == 0 ? Status.OK_STATUS : (IStatus) result; + } + // first time through the validator hasn't been initialized so try and create it + if (validator == null) + initializeValidator(); + // we were unable to initialize the validator. Validation has been turned off and + // a warning has already been logged so just return. + if (validator == null) + return Status.OK_STATUS; + // otherwise call the API and throw an exception if appropriate + final IStatus[] status = new IStatus[1]; + ISafeRunnable body = new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + status[0] = new ResourceStatus(IStatus.ERROR, null, Messages.resources_errorValidator, exception); + } + + @Override + public void run() throws Exception { + Object c = context; + //must null any reference to FileModificationValidationContext for backwards compatibility + if (!(validator instanceof FileModificationValidator)) + if (c instanceof FileModificationValidationContext) + c = null; + status[0] = validator.validateEdit(files, c); + } + }; + SafeRunner.run(body); + return status[0]; + } + + @Override + public IStatus validateLinkLocation(IResource resource, IPath unresolvedLocation) { + return locationValidator.validateLinkLocation(resource, unresolvedLocation); + } + + @Override + public IStatus validateLinkLocationURI(IResource resource, URI unresolvedLocation) { + return locationValidator.validateLinkLocationURI(resource, unresolvedLocation); + } + + @Override + public IStatus validateName(String segment, int type) { + return locationValidator.validateName(segment, type); + } + + @Override + public IStatus validateNatureSet(String[] natureIds) { + return natureManager.validateNatureSet(natureIds); + } + + @Override + public IStatus validatePath(String path, int type) { + return locationValidator.validatePath(path, type); + } + + @Override + public IStatus validateProjectLocation(IProject context, IPath location) { + return locationValidator.validateProjectLocation(context, location); + } + + @Override + public IStatus validateProjectLocationURI(IProject project, URI location) { + return locationValidator.validateProjectLocationURI(project, location); + } + + /** + * Internal method. To be called only from the following methods: + *
                    + *
                  • IFile#appendContents
                  • + *
                  • IFile#setContents(InputStream, boolean, boolean, IProgressMonitor)
                  • + *
                  • IFile#setContents(IFileState, boolean, boolean, IProgressMonitor)
                  • + *
                  + * + * @see IFileModificationValidator#validateSave(IFile) + */ + protected void validateSave(final IFile file) throws CoreException { + // if validation is turned off then just return + if (!shouldValidate) + return; + // first time through the validator hasn't been initialized so try and create it + if (validator == null) + initializeValidator(); + // we were unable to initialize the validator. Validation has been turned off and + // a warning has already been logged so just return. + if (validator == null) + return; + // otherwise call the API and throw an exception if appropriate + final IStatus[] status = new IStatus[1]; + ISafeRunnable body = new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + status[0] = new ResourceStatus(IStatus.ERROR, null, Messages.resources_errorValidator, exception); + } + + @Override + public void run() throws Exception { + status[0] = validator.validateSave(file); + } + }; + SafeRunner.run(body); + if (!status[0].isOK()) + throw new ResourceException(status[0]); + } + + @Override + public IStatus validateFiltered(IResource resource) { + try { + if (((Resource) resource).isFilteredWithException(true)) + return new ResourceStatus(IStatus.ERROR, Messages.resources_errorResourceIsFiltered); + } catch (CoreException e) { + // if we can't validate it, we return OK + } + return Status.OK_STATUS; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java new file mode 100644 index 0000000000..78d21a7d57 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.resources.IWorkspaceDescription; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.preferences.DefaultScope; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; + +/** + * @see IWorkspaceDescription + */ +public class WorkspaceDescription extends ModelObject implements IWorkspaceDescription { + protected boolean autoBuilding; + protected String[] buildOrder; + protected long fileStateLongevity; + protected int maxBuildIterations; + protected int maxFileStates; + protected long maxFileStateSize; + protected boolean applyFileStatePolicy; + private long snapshotInterval; + protected int operationsPerSnapshot; + protected long deltaExpiration; + + public WorkspaceDescription(String name) { + super(name); + // initialize based on the values in the default preferences + IEclipsePreferences node = DefaultScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES); + autoBuilding = node.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING, PreferenceInitializer.PREF_AUTO_BUILDING_DEFAULT); + maxBuildIterations = node.getInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS, PreferenceInitializer.PREF_MAX_BUILD_ITERATIONS_DEFAULT); + applyFileStatePolicy = node.getBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY, PreferenceInitializer.PREF_APPLY_FILE_STATE_POLICY_DEFAULT); + fileStateLongevity = node.getLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, PreferenceInitializer.PREF_FILE_STATE_LONGEVITY_DEFAULT); + maxFileStates = node.getInt(ResourcesPlugin.PREF_MAX_FILE_STATES, PreferenceInitializer.PREF_MAX_FILE_STATES_DEFAULT); + maxFileStateSize = node.getLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, PreferenceInitializer.PREF_MAX_FILE_STATE_SIZE_DEFAULT); + snapshotInterval = node.getLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL, PreferenceInitializer.PREF_SNAPSHOT_INTERVAL_DEFAULT); + operationsPerSnapshot = node.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT, PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT_DEFAULT); + deltaExpiration = node.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION, PreferenceInitializer.PREF_DELTA_EXPIRATION_DEFAULT); + } + + /** + * @see IWorkspaceDescription#getBuildOrder() + */ + @Override + public String[] getBuildOrder() { + return getBuildOrder(true); + } + + public String[] getBuildOrder(boolean makeCopy) { + if (buildOrder == null) + return null; + return makeCopy ? (String[]) buildOrder.clone() : buildOrder; + } + + public long getDeltaExpiration() { + return deltaExpiration; + } + + public void setDeltaExpiration(long value) { + deltaExpiration = value; + } + + /** + * @see IWorkspaceDescription#getFileStateLongevity() + */ + @Override + public long getFileStateLongevity() { + return fileStateLongevity; + } + + /** + * @see IWorkspaceDescription#getMaxBuildIterations() + */ + @Override + public int getMaxBuildIterations() { + return maxBuildIterations; + } + + /** + * @see IWorkspaceDescription#getMaxFileStates() + */ + @Override + public int getMaxFileStates() { + return maxFileStates; + } + + /** + * @see IWorkspaceDescription#getMaxFileStateSize() + */ + @Override + public long getMaxFileStateSize() { + return maxFileStateSize; + } + + /** + * @see IWorkspaceDescription#isApplyFileStatePolicy() + */ + @Override + public boolean isApplyFileStatePolicy() { + return applyFileStatePolicy; + } + + public int getOperationsPerSnapshot() { + return operationsPerSnapshot; + } + + /** + * @see IWorkspaceDescription#getSnapshotInterval() + */ + @Override + public long getSnapshotInterval() { + return snapshotInterval; + } + + public void internalSetBuildOrder(String[] value) { + buildOrder = value; + } + + /** + * @see IWorkspaceDescription#isAutoBuilding() + */ + @Override + public boolean isAutoBuilding() { + return autoBuilding; + } + + public void setOperationsPerSnapshot(int value) { + operationsPerSnapshot = value; + } + + /** + * @see IWorkspaceDescription#setAutoBuilding(boolean) + */ + @Override + public void setAutoBuilding(boolean value) { + autoBuilding = value; + } + + /** + * @see IWorkspaceDescription#setBuildOrder(String[]) + */ + @Override + public void setBuildOrder(String[] value) { + buildOrder = (value == null) ? null : (String[]) value.clone(); + } + + /** + * @see IWorkspaceDescription#setFileStateLongevity(long) + */ + @Override + public void setFileStateLongevity(long time) { + fileStateLongevity = time; + } + + /** + * @see IWorkspaceDescription#setMaxBuildIterations(int) + */ + @Override + public void setMaxBuildIterations(int number) { + maxBuildIterations = number; + } + + /** + * @see IWorkspaceDescription#setMaxFileStates(int) + */ + @Override + public void setMaxFileStates(int number) { + maxFileStates = number; + } + + /** + * @see IWorkspaceDescription#setMaxFileStateSize(long) + */ + @Override + public void setMaxFileStateSize(long size) { + maxFileStateSize = size; + } + + /** + * @see IWorkspaceDescription#setApplyFileStatePolicy(boolean) + */ + @Override + public void setApplyFileStatePolicy(boolean apply) { + applyFileStatePolicy = apply; + } + + /** + * @see IWorkspaceDescription#setSnapshotInterval(long) + */ + @Override + public void setSnapshotInterval(long snapshotInterval) { + this.snapshotInterval = snapshotInterval; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java new file mode 100644 index 0000000000..dfea8b6c06 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import javax.xml.parsers.*; +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.resources.IResourceStatus; +import org.eclipse.core.runtime.IPath; +import org.eclipse.osgi.util.NLS; +import org.w3c.dom.*; +import org.xml.sax.SAXException; + +/** + * This class contains legacy code only. It is being used to read workspace + * descriptions which are obsolete. + */ +public class WorkspaceDescriptionReader implements IModelObjectConstants { + /** constants */ + protected static final String[] EMPTY_STRING_ARRAY = new String[0]; + + public WorkspaceDescriptionReader() { + super(); + } + + protected String getString(Node target, String tagName) { + Node node = searchNode(target, tagName); + return node != null ? (node.getFirstChild() == null ? null : node.getFirstChild().getNodeValue()) : null; + } + + protected String[] getStrings(Node target) { + if (target == null) + return null; + NodeList list = target.getChildNodes(); + if (list.getLength() == 0) + return EMPTY_STRING_ARRAY; + List result = new ArrayList<>(list.getLength()); + for (int i = 0; i < list.getLength(); i++) { + Node node = list.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) + result.add(read(node.getChildNodes().item(0))); + } + return result.toArray(new String[result.size()]); + } + + /** + * A value was discovered in the workspace description file that was not a number. + * Log the exception. + */ + private void logNumberFormatException(String value, NumberFormatException e) { + String msg = NLS.bind(Messages.resources_readWorkspaceMetaValue, value); + Policy.log(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, null, msg, e)); + } + + public Object read(InputStream input) { + try { + DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = parser.parse(input); + return read(document.getFirstChild()); + } catch (IOException e) { + // ignore + } catch (SAXException e) { + // ignore + } catch (ParserConfigurationException e) { + // ignore + } + return null; + } + + public Object read(IPath location, IPath tempLocation) throws IOException { + SafeFileInputStream file = new SafeFileInputStream(location.toOSString(), tempLocation.toOSString()); + try { + return read(file); + } finally { + file.close(); + } + } + + protected Object read(Node node) { + if (node == null) + return null; + switch (node.getNodeType()) { + case Node.ELEMENT_NODE : + if (node.getNodeName().equals(WORKSPACE_DESCRIPTION)) + return readWorkspaceDescription(node); + case Node.TEXT_NODE : + String value = node.getNodeValue(); + return value == null ? null : value.trim(); + default : + return node.toString(); + } + } + + /** + * read (String, String) hashtables + */ + protected WorkspaceDescription readWorkspaceDescription(Node node) { + // get values + String name = getString(node, NAME); + String autobuild = getString(node, AUTOBUILD); + String snapshotInterval = getString(node, SNAPSHOT_INTERVAL); + String applyFileStatePolicy = getString(node, APPLY_FILE_STATE_POLICY); + String fileStateLongevity = getString(node, FILE_STATE_LONGEVITY); + String maxFileStateSize = getString(node, MAX_FILE_STATE_SIZE); + String maxFileStates = getString(node, MAX_FILE_STATES); + String[] buildOrder = getStrings(searchNode(node, BUILD_ORDER)); + + // build instance + //invalid values are skipped and defaults are used instead + WorkspaceDescription description = new WorkspaceDescription(name); + if (autobuild != null) + //if in doubt (value is corrupt) we want autobuild on + description.setAutoBuilding(!autobuild.equals(Integer.toString(0))); + if (applyFileStatePolicy != null) + //if in doubt (value is corrupt) we want applyFileLimits on + description.setApplyFileStatePolicy(!applyFileStatePolicy.equals(Integer.toString(0))); + try { + if (fileStateLongevity != null) + description.setFileStateLongevity(Long.parseLong(fileStateLongevity)); + } catch (NumberFormatException e) { + logNumberFormatException(fileStateLongevity, e); + } + try { + if (maxFileStateSize != null) + description.setMaxFileStateSize(Long.parseLong(maxFileStateSize)); + } catch (NumberFormatException e) { + logNumberFormatException(maxFileStateSize, e); + } + try { + if (maxFileStates != null) + description.setMaxFileStates(Integer.parseInt(maxFileStates)); + } catch (NumberFormatException e) { + logNumberFormatException(maxFileStates, e); + } + if (buildOrder != null) + description.internalSetBuildOrder(buildOrder); + try { + if (snapshotInterval != null) + description.setSnapshotInterval(Long.parseLong(snapshotInterval)); + } catch (NumberFormatException e) { + logNumberFormatException(snapshotInterval, e); + } + return description; + } + + protected Node searchNode(Node target, String tagName) { + NodeList list = target.getChildNodes(); + for (int i = 0; i < list.getLength(); i++) { + if (list.item(i).getNodeName().equals(tagName)) + return list.item(i); + } + return null; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java new file mode 100644 index 0000000000..36909630c6 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java @@ -0,0 +1,257 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.util.*; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Preferences; +import org.eclipse.core.runtime.Preferences.PropertyChangeEvent; + +/** + * This class provides the same interface as WorkspaceDescription + * but instead of changing/obtaining values from its internal state, it + * changes/obtains properties in/from the workspace plug-in's preferences. + * + * Note: for performance reasons, some frequently called accessor methods are + * reading a cached value from the super class instead of reading the + * corresponding property preference store. To keep the cache synchronized with + * the preference store, a property change listener is used. + */ +public class WorkspacePreferences extends WorkspaceDescription { + + public final static String PROJECT_SEPARATOR = "/"; //$NON-NLS-1$ + + private Preferences preferences; + + /** + * Helper method that converts a string string array {"string1"," + * string2",..."stringN"} to a string in the form "string1,string2,... + * stringN". + */ + public static String convertStringArraytoString(String[] array) { + if (array == null || array.length == 0) + return ""; //$NON-NLS-1$ + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < array.length; i++) { + sb.append(array[i]); + sb.append(PROJECT_SEPARATOR); + } + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); + } + + /** + * Helper method that converts a string in the form "string1,string2,... + * stringN" to a string array {"string1","string2",..."stringN"}. + */ + public static String[] convertStringToStringArray(String string, String separator) { + List list = new ArrayList<>(); + for (StringTokenizer tokenizer = new StringTokenizer(string, separator); tokenizer.hasMoreTokens();) + list.add(tokenizer.nextToken()); + return list.toArray(new String[list.size()]); + } + + /** + * Helper method that copies all attributes from a workspace description + * object to another. + */ + private static void copyFromTo(WorkspaceDescription source, WorkspaceDescription target) { + target.setAutoBuilding(source.isAutoBuilding()); + target.setBuildOrder(source.getBuildOrder()); + target.setMaxBuildIterations(source.getMaxBuildIterations()); + target.setApplyFileStatePolicy(source.isApplyFileStatePolicy()); + target.setFileStateLongevity(source.getFileStateLongevity()); + target.setMaxFileStates(source.getMaxFileStates()); + target.setMaxFileStateSize(source.getMaxFileStateSize()); + target.setSnapshotInterval(source.getSnapshotInterval()); + target.setOperationsPerSnapshot(source.getOperationsPerSnapshot()); + target.setDeltaExpiration(source.getDeltaExpiration()); + } + + public WorkspacePreferences() { + super("Workspace"); //$NON-NLS-1$ + this.preferences = ResourcesPlugin.getPlugin().getPluginPreferences(); + + final String version = preferences.getString(ICoreConstants.PREF_VERSION_KEY); + if (!ICoreConstants.PREF_VERSION.equals(version)) + upgradeVersion(version); + + // initialize cached preferences (for better performance) + super.setAutoBuilding(preferences.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING)); + super.setSnapshotInterval(preferences.getInt(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL)); + super.setMaxBuildIterations(preferences.getInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS)); + super.setApplyFileStatePolicy(preferences.getBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY)); + super.setMaxFileStates(preferences.getInt(ResourcesPlugin.PREF_MAX_FILE_STATES)); + super.setMaxFileStateSize(preferences.getLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE)); + super.setFileStateLongevity(preferences.getLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY)); + super.setOperationsPerSnapshot(preferences.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT)); + super.setDeltaExpiration(preferences.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION)); + + // This property listener ensures we are being updated properly when changes + // are done directly to the preference store. + preferences.addPropertyChangeListener(new Preferences.IPropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + synchronizeWithPreferences(event.getProperty()); + } + }); + } + + @Override + public Object clone() { + // should never be called - throws an exception to avoid using a + // WorkspacePreferences when using WorkspaceDescription was the real + // intention (this class offers a different protocol for copying state). + throw new UnsupportedOperationException("clone() is not supported in " + getClass().getName()); //$NON-NLS-1$ + } + + public void copyFrom(WorkspaceDescription source) { + copyFromTo(source, this); + } + + public void copyTo(WorkspaceDescription target) { + copyFromTo(this, target); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#getBuildOrder() + */ + @Override + public String[] getBuildOrder() { + boolean defaultBuildOrder = preferences.getBoolean(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER); + if (defaultBuildOrder) + return null; + return convertStringToStringArray(preferences.getString(ResourcesPlugin.PREF_BUILD_ORDER), PROJECT_SEPARATOR); + } + + /** + * @see org.eclipse.core.internal.resources. + * WorkspaceDescription#getBuildOrder(boolean) + */ + @Override + public String[] getBuildOrder(boolean makeCopy) { + //note that since this is stored in the preference store, we are creating + //a new copy of the string array on every access anyway + return getBuildOrder(); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setAutoBuilding(boolean) + */ + @Override + public void setAutoBuilding(boolean value) { + preferences.setValue(ResourcesPlugin.PREF_AUTO_BUILDING, value); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setBuildOrder(String[]) + */ + @Override + public void setBuildOrder(String[] value) { + preferences.setValue(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER, value == null); + preferences.setValue(ResourcesPlugin.PREF_BUILD_ORDER, convertStringArraytoString(value)); + } + + @Override + public void setDeltaExpiration(long value) { + preferences.setValue(PreferenceInitializer.PREF_DELTA_EXPIRATION, value); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setApplyFileStatePolicy(boolean) + */ + @Override + public void setApplyFileStatePolicy(boolean apply) { + preferences.setValue(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY, apply); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setFileStateLongevity(long) + */ + @Override + public void setFileStateLongevity(long time) { + preferences.setValue(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, time); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxBuildIterations(int) + */ + @Override + public void setMaxBuildIterations(int number) { + preferences.setValue(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS, number); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxFileStates(int) + */ + @Override + public void setMaxFileStates(int number) { + preferences.setValue(ResourcesPlugin.PREF_MAX_FILE_STATES, number); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxFileStateSize(long) + */ + @Override + public void setMaxFileStateSize(long size) { + preferences.setValue(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, size); + } + + @Override + public void setOperationsPerSnapshot(int value) { + preferences.setValue(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT, value); + } + + /** + * @see org.eclipse.core.resources.IWorkspaceDescription#setSnapshotInterval(long) + */ + @Override + public void setSnapshotInterval(long delay) { + preferences.setValue(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL, delay); + } + + protected void synchronizeWithPreferences(String property) { + // do not use the value in the event - may be a string instead + // of the expected type. Retrieve it from the preferences store + // using the type-specific method + if (property.equals(ResourcesPlugin.PREF_AUTO_BUILDING)) + super.setAutoBuilding(preferences.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING)); + else if (property.equals(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL)) + super.setSnapshotInterval(preferences.getLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL)); + else if (property.equals(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS)) + super.setMaxBuildIterations(preferences.getInt(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS)); + else if (property.equals(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY)) + super.setApplyFileStatePolicy(preferences.getBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY)); + else if (property.equals(ResourcesPlugin.PREF_MAX_FILE_STATES)) + super.setMaxFileStates(preferences.getInt(ResourcesPlugin.PREF_MAX_FILE_STATES)); + else if (property.equals(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE)) + super.setMaxFileStateSize(preferences.getLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE)); + else if (property.equals(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY)) + super.setFileStateLongevity(preferences.getLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY)); + else if (property.equals(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT)) + super.setOperationsPerSnapshot(preferences.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT)); + else if (property.equals(PreferenceInitializer.PREF_DELTA_EXPIRATION)) + super.setDeltaExpiration(preferences.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION)); + } + + private void upgradeVersion(String oldVersion) { + if (oldVersion.length() == 0) { + //only need to convert the build order if we are not using the default order + if (!preferences.getBoolean(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER)) { + String oldOrder = preferences.getString(ResourcesPlugin.PREF_BUILD_ORDER); + setBuildOrder(convertStringToStringArray(oldOrder, ":")); //$NON-NLS-1$ + } + } + preferences.setValue(ICoreConstants.PREF_VERSION_KEY, ICoreConstants.PREF_VERSION); + } +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java new file mode 100644 index 0000000000..7007b33f7c --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java @@ -0,0 +1,295 @@ +/******************************************************************************* + * 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.net.URI; +import java.util.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.utils.FileUtil; +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 WorkspaceRoot extends Container implements IWorkspaceRoot { + /** + * As an optimization, we store a table of project handles + * that have been requested from this root. This maps project + * name strings to project handles. + */ + private final Map projectTable = Collections.synchronizedMap(new HashMap(16)); + + /** + * Cache of the canonicalized platform location. + */ + private final IPath workspaceLocation; + + protected WorkspaceRoot(IPath path, Workspace container) { + super(path, container); + Assert.isTrue(path.equals(Path.ROOT)); + workspaceLocation = FileUtil.canonicalPath(Platform.getLocation()); + Assert.isNotNull(workspaceLocation); + } + + @Override + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + updateFlags |= deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT; + delete(updateFlags, monitor); + } + + @Override + public void delete(boolean force, IProgressMonitor monitor) throws CoreException { + int updateFlags = force ? IResource.FORCE : IResource.NONE; + delete(updateFlags, monitor); + } + + @Override + public boolean exists(int flags, boolean checkType) { + return true; + } + + @Deprecated + @Override + public IContainer[] findContainersForLocation(IPath location) { + return findContainersForLocationURI(URIUtil.toURI(location.makeAbsolute())); + } + + @Override + public IContainer[] findContainersForLocationURI(URI location) { + return findContainersForLocationURI(location, NONE); + } + + @Override + public IContainer[] findContainersForLocationURI(URI location, int memberFlags) { + if (!location.isAbsolute()) + throw new IllegalArgumentException(); + return (IContainer[]) getLocalManager().allResourcesFor(location, false, memberFlags); + } + + @Deprecated + @Override + public IFile[] findFilesForLocation(IPath location) { + return findFilesForLocationURI(URIUtil.toURI(location.makeAbsolute())); + } + + @Override + public IFile[] findFilesForLocationURI(URI location) { + return findFilesForLocationURI(location, NONE); + } + + @Override + public IFile[] findFilesForLocationURI(URI location, int memberFlags) { + if (!location.isAbsolute()) + throw new IllegalArgumentException(); + return (IFile[]) getLocalManager().allResourcesFor(location, true, memberFlags); + } + + @Override + public IContainer getContainerForLocation(IPath location) { + return getLocalManager().containerForLocation(location); + } + + @Override + public String getDefaultCharset(boolean checkImplicit) { + if (checkImplicit) + return ResourcesPlugin.getEncoding(); + String enc = ResourcesPlugin.getPlugin().getPluginPreferences().getString(ResourcesPlugin.PREF_ENCODING); + return enc == null || enc.length() == 0 ? null : enc; + } + + @Override + public IFile getFileForLocation(IPath location) { + return getLocalManager().fileForLocation(location); + } + + @Override + public long getLocalTimeStamp() { + return IResource.NULL_STAMP; + } + + @Override + public IPath getLocation() { + return workspaceLocation; + } + + @Override + public String getName() { + return ""; //$NON-NLS-1$ + } + + @Override + public IContainer getParent() { + return null; + } + + @Override + public IProject getProject() { + return null; + } + + @Override + public IProject getProject(String name) { + //first check our project cache + Project result = projectTable.get(name); + if (result == null) { + IPath projectPath = new Path(null, name).makeAbsolute(); + String message = "Path for project must have only one segment."; //$NON-NLS-1$ + Assert.isLegal(projectPath.segmentCount() == ICoreConstants.PROJECT_SEGMENT_LENGTH, message); + //try to get the project using a canonical name + String canonicalName = projectPath.lastSegment(); + result = projectTable.get(canonicalName); + if (result != null) + return result; + result = new Project(projectPath, workspace); + projectTable.put(canonicalName, result); + } + return result; + } + + @Override + public IPath getProjectRelativePath() { + return Path.EMPTY; + } + + @Override + public IProject[] getProjects() { + return getProjects(IResource.NONE); + } + + @Override + public IProject[] getProjects(int memberFlags) { + IResource[] roots = getChildren(memberFlags); + IProject[] result = new IProject[roots.length]; + try { + System.arraycopy(roots, 0, result, 0, roots.length); + } catch (ArrayStoreException ex) { + // Shouldn't happen since only projects should be children of the workspace root + for (int i = 0; i < roots.length; i++) { + if (roots[i].getType() != IResource.PROJECT) + Policy.log(IStatus.ERROR, NLS.bind("{0} is an invalid child of the workspace root.", //$NON-NLS-1$ + roots[i]), null); + + } + throw ex; + } + return result; + } + + @Override + public int getType() { + return IResource.ROOT; + } + + @Override + public void internalSetLocal(boolean flag, int depth) throws CoreException { + // do nothing for the root, but call for its children + if (depth == IResource.DEPTH_ZERO) + return; + if (depth == IResource.DEPTH_ONE) + depth = IResource.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++) + ((Resource) children[i]).internalSetLocal(flag, depth); + } + + @Override + public boolean isDerived(int options) { + return false;//the root is never derived + } + + @Override + public boolean isHidden() { + return false;//the root is never hidden + } + + @Override + public boolean isHidden(int options) { + return false;//the root is never hidden + } + + @Override + public boolean isTeamPrivateMember(int options) { + return false;//the root is never a team private member + } + + @Override + public boolean isLinked(int options) { + return false;//the root is never linked + } + + @Deprecated + @Override + public boolean isLocal(int depth) { + // the flags parameter is ignored for the workspace root so pass anything + return isLocal(-1, depth); + } + + @Deprecated + @Override + public boolean isLocal(int flags, int depth) { + // don't check the flags....workspace root is always local + 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 boolean isPhantom() { + return false; + } + + @Deprecated + @Override + public void setDefaultCharset(String charset) { + // directly change the Resource plugin's preference for encoding + Preferences resourcesPreferences = ResourcesPlugin.getPlugin().getPluginPreferences(); + if (charset != null) + resourcesPreferences.setValue(ResourcesPlugin.PREF_ENCODING, charset); + else + resourcesPreferences.setToDefault(ResourcesPlugin.PREF_ENCODING); + } + + @Override + public void setHidden(boolean isHidden) { + //workspace root cannot be set hidden + } + + @Override + public long setLocalTimeStamp(long value) { + if (value < 0) + throw new IllegalArgumentException("Illegal time stamp: " + value); //$NON-NLS-1$ + //can't set local time for root + return value; + } + + @Deprecated + @Override + public void setReadOnly(boolean readonly) { + //can't set the root read only + } + + @Override + public void touch(IProgressMonitor monitor) { + // do nothing for the workspace root + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java new file mode 100644 index 0000000000..db7f637c58 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 - Initial API and implementation + * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.watson.ElementTree; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.osgi.util.NLS; + +/** + * Default tree reader that does not read anything. This is used in cases + * where the tree format is unknown (for example when opening a workspace + * from a future version). + */ +public abstract class WorkspaceTreeReader { + + /** + * Configuration setting to have an existing workspace + * project name take precedence over data being read, + * when set to true. + */ + protected boolean renameProjectNode; + + /** + * Returns the tree reader associated with the given tree version number. + * @param renameProjectNode if true, set up the reader to have + * the existing root node in the workspace (that is, the project being + * read into) take precedence over the root node being read from the file. + * Otherwise, the tree file is read unmodified. + */ + public static WorkspaceTreeReader getReader(Workspace workspace, int version, boolean renameProjectNode) throws CoreException { + WorkspaceTreeReader w = null; + switch (version) { + case ICoreConstants.WORKSPACE_TREE_VERSION_1 : + w = new WorkspaceTreeReader_1(workspace); + w.renameProjectNode = renameProjectNode; + return w; + case ICoreConstants.WORKSPACE_TREE_VERSION_2 : + w = new WorkspaceTreeReader_2(workspace); + w.renameProjectNode = renameProjectNode; + return w; + default : + // Unknown tree version - fail to read the tree + String msg = NLS.bind(Messages.resources_format, version); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, msg, null); + } + } + + /** + * Returns the tree reader associated with the given tree version number. + */ + public static WorkspaceTreeReader getReader(Workspace workspace, int version) throws CoreException { + return getReader(workspace, version, false); + } + + /** + * Returns a snapshot from the stream. This default implementation does nothing. + */ + public abstract ElementTree readSnapshotTree(DataInputStream input, ElementTree complete, IProgressMonitor monitor) throws CoreException; + + /** + * Reads all workspace trees from the stream. This default implementation does nothing. + */ + public abstract void readTree(DataInputStream input, IProgressMonitor monitor) throws CoreException; + + /** + * Reads a project's trees from the stream. This default implementation does nothing. + */ + public abstract void readTree(IProject project, DataInputStream input, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java new file mode 100644 index 0000000000..d8d2362e01 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java @@ -0,0 +1,271 @@ +/******************************************************************************* + * 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 + * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.*; +import java.util.*; +import org.eclipse.core.internal.events.BuilderPersistentInfo; +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.internal.watson.ElementTreeReader; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; + +/** + * Reads version 1 of the workspace tree file format. + */ +public class WorkspaceTreeReader_1 extends WorkspaceTreeReader { + protected Workspace workspace; + + public WorkspaceTreeReader_1(Workspace workspace) { + this.workspace = workspace; + } + + protected int getVersion() { + return ICoreConstants.WORKSPACE_TREE_VERSION_1; + } + + protected void linkBuildersToTrees(List buildersToBeLinked, ElementTree[] trees, int index, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + ArrayList infos = null; + String projectName = null; + for (int i = 0; i < buildersToBeLinked.size(); i++) { + BuilderPersistentInfo info = buildersToBeLinked.get(i); + if (!info.getProjectName().equals(projectName)) { + if (infos != null) { // if it is not the first iteration + IProject project = workspace.getRoot().getProject(projectName); + workspace.getBuildManager().setBuildersPersistentInfo(project, infos); + } + projectName = info.getProjectName(); + infos = new ArrayList<>(5); + } + info.setLastBuildTree(trees[index++]); + infos.add(info); + } + if (infos != null) { + IProject project = workspace.getRoot().getProject(projectName); + workspace.getBuildManager().setBuildersPersistentInfo(project, infos); + } + } finally { + monitor.done(); + } + } + + protected void linkPluginsSavedStateToTrees(List states, ElementTree[] trees, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + for (int i = 0; i < states.size(); i++) { + SavedState state = states.get(i); + // If the tree is too old (depends on the policy), the plug-in should not + // get it back as a delta. It is expensive to maintain this information too long. + final SaveManager saveManager = workspace.getSaveManager(); + if (!saveManager.isOldPluginTree(state.pluginId)) { + state.oldTree = trees[i]; + } else { + //clear information for this plugin from master table + saveManager.clearDeltaExpiration(state.pluginId); + } + } + } finally { + monitor.done(); + } + } + + protected BuilderPersistentInfo readBuilderInfo(IProject project, DataInputStream input, int index) throws IOException { + //read the project name + String projectName = input.readUTF(); + //use the name of the project handle if available + if (project != null) + projectName = project.getName(); + String builderName = input.readUTF(); + return new BuilderPersistentInfo(projectName, builderName, index); + } + + protected void readBuildersPersistentInfo(IProject project, DataInputStream input, List builders, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + int builderCount = input.readInt(); + for (int i = 0; i < builderCount; i++) + builders.add(readBuilderInfo(project, input, i)); + } finally { + monitor.done(); + } + } + + protected void readPluginsSavedStates(DataInputStream input, HashMap savedStates, List plugins, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + int stateCount = input.readInt(); + for (int i = 0; i < stateCount; i++) { + String pluginId = input.readUTF(); + SavedState state = new SavedState(workspace, pluginId, null, null); + savedStates.put(pluginId, state); + plugins.add(state); + } + } finally { + monitor.done(); + } + } + + @Override + public ElementTree readSnapshotTree(DataInputStream input, ElementTree complete, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_readingSnap; + monitor.beginTask(message, Policy.totalWork); + ElementTreeReader reader = new ElementTreeReader(workspace.getSaveManager()); + while (input.available() > 0) { + readWorkspaceFields(input, Policy.subMonitorFor(monitor, Policy.totalWork / 2)); + complete = reader.readDelta(complete, input); + try { + // make sure each snapshot is read by the correct reader + int version = input.readInt(); + if (version != getVersion()) + return WorkspaceTreeReader.getReader(workspace, version).readSnapshotTree(input, complete, monitor); + } catch (EOFException e) { + break; + } + } + return complete; + } catch (IOException e) { + message = Messages.resources_readWorkspaceSnap; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + @Override + public void readTree(DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, Policy.totalWork); + readWorkspaceFields(input, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + HashMap savedStates = new HashMap<>(20); + List pluginsToBeLinked = new ArrayList<>(20); + readPluginsSavedStates(input, savedStates, pluginsToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + workspace.getSaveManager().setPluginsSavedState(savedStates); + + List buildersToBeLinked = new ArrayList<>(20); + readBuildersPersistentInfo(null, input, buildersToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + ElementTree[] trees = readTrees(Path.ROOT, input, Policy.subMonitorFor(monitor, Policy.opWork * 40 / 100)); + linkPluginsSavedStateToTrees(pluginsToBeLinked, trees, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + linkBuildersToTrees(buildersToBeLinked, trees, pluginsToBeLinked.size(), Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + } catch (IOException e) { + message = Messages.resources_readWorkspaceTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + @Override + public void readTree(IProject project, DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, 10); + /* read the number of builders */ + int numBuilders = input.readInt(); + + /* read in the list of builder names */ + String[] builderNames = new String[numBuilders]; + for (int i = 0; i < numBuilders; i++) { + String builderName = input.readUTF(); + builderNames[i] = builderName; + } + monitor.worked(1); + + /* read and link the trees */ + ElementTree[] trees = readTrees(project.getFullPath(), input, Policy.subMonitorFor(monitor, 8)); + + /* map builder names to trees */ + if (numBuilders > 0) { + ArrayList infos = new ArrayList<>(trees.length * 2 + 1); + for (int i = 0; i < numBuilders; i++) { + BuilderPersistentInfo info = new BuilderPersistentInfo(project.getName(), builderNames[i], -1); + info.setLastBuildTree(trees[i]); + infos.add(info); + } + workspace.getBuildManager().setBuildersPersistentInfo(project, infos); + } + monitor.worked(1); + + } catch (IOException e) { + message = Messages.resources_readProjectTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + /** + * Read trees from disk and link them to the workspace tree. + */ + protected ElementTree[] readTrees(IPath root, DataInputStream input, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + String message = Messages.resources_reading; + monitor.beginTask(message, 4); + ElementTreeReader treeReader = new ElementTreeReader(workspace.getSaveManager()); + String newProjectName = ""; //$NON-NLS-1$ + if (renameProjectNode) { + //have the existing project name (path to import into) take precedence over what we read + newProjectName = root.segment(0); + } + ElementTree[] trees = treeReader.readDeltaChain(input, newProjectName); + monitor.worked(3); + if (root.isRoot()) { + //Don't need to link because we're reading the whole workspace. + //The last tree in the chain is the complete tree. + ElementTree newTree = trees[trees.length - 1]; + newTree.setTreeData(workspace.tree.getTreeData()); + workspace.tree = newTree; + } else { + //splice the restored tree into the current set of trees + workspace.linkTrees(root, trees); + } + monitor.worked(1); + return trees; + } finally { + monitor.done(); + } + } + + protected void readWorkspaceFields(DataInputStream input, IProgressMonitor monitor) throws IOException, CoreException { + monitor = Policy.monitorFor(monitor); + try { + // read the node id + workspace.nextNodeId = input.readLong(); + // read the modification stamp (no longer used) + input.readLong(); + // read the next marker id + workspace.nextMarkerId = input.readLong(); + // read the synchronizer's registered sync partners + ((Synchronizer) workspace.getSynchronizer()).readPartners(input); + } finally { + monitor.done(); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java new file mode 100644 index 0000000000..b0e0236029 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java @@ -0,0 +1,219 @@ +/******************************************************************************* + * 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 + * Baltasar Belyavsky (Texas Instruments) - [361675] Order mismatch when saving/restoring workspace trees + * Broadcom Corporation - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.*; +import org.eclipse.core.internal.events.BuilderPersistentInfo; +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.IProject; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.runtime.*; + +/** + * Reads version 2 of the workspace tree file format. + * + * This version differs from version 1 in the amount of information that is persisted + * for each builder. Version 1 only stored builder names and trees. Version + * 2 stores builder names, project names, trees, and interesting projects for + * each builder. + *

                  + * Since 3.7 support has been added for persisting multiple delta trees for + * multi-configuration builders. + *

                  + *

                  + * To achieve backwards compatibility, the new additional information is + * appended to the existing workspace tree file. This allows the workspace + * to be opened, and function, with older eclipse products. + *

                  + */ +public class WorkspaceTreeReader_2 extends WorkspaceTreeReader_1 { + + private List builderInfos; + + public WorkspaceTreeReader_2(Workspace workspace) { + super(workspace); + } + + @Override + protected int getVersion() { + return ICoreConstants.WORKSPACE_TREE_VERSION_2; + } + + /* + * overwritten from WorkspaceTreeReader_1 + */ + @Override + protected void readBuildersPersistentInfo(IProject project, DataInputStream input, List builders, IProgressMonitor monitor) throws IOException { + monitor = Policy.monitorFor(monitor); + try { + int builderCount = input.readInt(); + for (int i = 0; i < builderCount; i++) { + BuilderPersistentInfo info = readBuilderInfo(project, input, i); + // read interesting projects + int n = input.readInt(); + IProject[] projects = new IProject[n]; + for (int j = 0; j < n; j++) + projects[j] = workspace.getRoot().getProject(input.readUTF()); + info.setInterestingProjects(projects); + builders.add(info); + } + } finally { + monitor.done(); + } + } + + /** + * Read a workspace tree storing information about multiple projects. + * Overrides {@link WorkspaceTreeReader_1#readTree(DataInputStream, IProgressMonitor)} + */ + @Override + public void readTree(DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, Policy.totalWork); + + builderInfos = new ArrayList<>(20); + + // Read the version 2 part of the file, but don't set the builder info in + // the projects. Store it in builderInfos instead. + readWorkspaceFields(input, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); + + HashMap savedStates = new HashMap<>(20); + List pluginsToBeLinked = new ArrayList<>(20); + readPluginsSavedStates(input, savedStates, pluginsToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + workspace.getSaveManager().setPluginsSavedState(savedStates); + + int treeIndex = pluginsToBeLinked.size(); + + List buildersToBeLinked = new ArrayList<>(20); + readBuildersPersistentInfo(null, input, buildersToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + final ElementTree[] trees = readTrees(Path.ROOT, input, Policy.subMonitorFor(monitor, Policy.opWork * 40 / 100)); + linkPluginsSavedStateToTrees(pluginsToBeLinked, trees, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + linkBuildersToTrees(buildersToBeLinked, trees, treeIndex, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + // Since 3.7: Read the per-configuration trees if available + if (input.available() > 0) { + treeIndex += buildersToBeLinked.size(); + + buildersToBeLinked.clear(); + readBuildersPersistentInfo(null, input, buildersToBeLinked, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + linkBuildersToTrees(buildersToBeLinked, trees, treeIndex, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); + + for (Iterator it = builderInfos.iterator(); it.hasNext();) + it.next().setConfigName(input.readUTF()); + } + + // Set the builder infos on the projects + setBuilderInfos(builderInfos); + + } catch (IOException e) { + message = Messages.resources_readProjectTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + /** + * Read a workspace tree storing information about a single project. + * Overrides {@link WorkspaceTreeReader_2#readTree(IProject, DataInputStream, IProgressMonitor)} + */ + @Override + public void readTree(IProject project, DataInputStream input, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + String message; + try { + message = Messages.resources_reading; + monitor.beginTask(message, 10); + + builderInfos = new ArrayList<>(20); + + // Read the version 2 part of the file, but don't set the builder info in + // the projects. It is stored in builderInfos instead. + + int treeIndex = 0; + + List buildersToBeLinked = new ArrayList<>(20); + readBuildersPersistentInfo(project, input, buildersToBeLinked, Policy.subMonitorFor(monitor, 1)); + + ElementTree[] trees = readTrees(project.getFullPath(), input, Policy.subMonitorFor(monitor, 8)); + linkBuildersToTrees(buildersToBeLinked, trees, treeIndex, Policy.subMonitorFor(monitor, 1)); + + // Since 3.7: Read the additional builder information + if (input.available() > 0) { + treeIndex += buildersToBeLinked.size(); + + List infos = new ArrayList<>(5); + readBuildersPersistentInfo(project, input, infos, Policy.subMonitorFor(monitor, 1)); + linkBuildersToTrees(infos, trees, treeIndex, Policy.subMonitorFor(monitor, 1)); + + for (Iterator it = builderInfos.iterator(); it.hasNext();) + it.next().setConfigName(input.readUTF()); + } + + // Set the builder info on the projects + setBuilderInfos(builderInfos); + + } catch (IOException e) { + message = Messages.resources_readProjectTree; + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e); + } finally { + monitor.done(); + } + } + + /** + * This implementation allows pre-3.7 version 2 and post-3.7 version 2 information to be loaded in separate passes. + * Links trees with the given builders, but does not add them to the projects. + * Overrides {@link WorkspaceTreeReader_1#linkBuildersToTrees(List, ElementTree[], int, IProgressMonitor)} + */ + @Override + protected void linkBuildersToTrees(List buildersToBeLinked, ElementTree[] trees, int index, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + for (int i = 0; i < buildersToBeLinked.size(); i++) { + BuilderPersistentInfo info = buildersToBeLinked.get(i); + info.setLastBuildTree(trees[index++]); + builderInfos.add(info); + } + } finally { + monitor.done(); + } + } + + /** + * Given a list of builder infos, group them by project and set them on the project. + */ + private void setBuilderInfos(List infos) { + Map> groupedInfos = new HashMap<>(); + for (Iterator it = infos.iterator(); it.hasNext();) { + BuilderPersistentInfo info = it.next(); + if (!groupedInfos.containsKey(info.getProjectName())) + groupedInfos.put(info.getProjectName(), new ArrayList()); + groupedInfos.get(info.getProjectName()).add(info); + } + for (Map.Entry> entry : groupedInfos.entrySet()) { + IProject proj = workspace.getRoot().getProject(entry.getKey()); + workspace.getBuildManager().setBuildersPersistentInfo(proj, entry.getValue()); + } + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java new file mode 100644 index 0000000000..692962741e --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2000, 2017 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.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * A simple XML writer. + */ +public class XMLWriter extends PrintWriter { + protected int tab; + protected String lineSeparator; + + /* constants */ + protected static final String XML_VERSION = ""; //$NON-NLS-1$ + + public XMLWriter(OutputStream output, String separator) { + super(new OutputStreamWriter(output, StandardCharsets.UTF_8)); + tab = 0; + lineSeparator = separator; + println(XML_VERSION); + } + + public void endTag(String name) { + tab--; + printTag('/' + name, null); + } + + @Override + public void println(String x) { + super.print(x); + super.print(lineSeparator); + } + + public void printSimpleTag(String name, Object value) { + if (value != null) { + printTag(name, null, true, false); + print(getEscaped(String.valueOf(value))); + printTag('/' + name, null, false, true); + } + } + + public void printTabulation() { + for (int i = 0; i < tab; i++) + super.print('\t'); + } + + public void printTag(String name, HashMap parameters) { + printTag(name, parameters, true, true); + } + + public void printTag(String name, HashMap parameters, boolean shouldTab, boolean newLine) { + StringBuilder sb = new StringBuilder(); + sb.append("<"); //$NON-NLS-1$ + sb.append(name); + if (parameters != null) + for (Map.Entry entry : parameters.entrySet()) { + sb.append(" "); //$NON-NLS-1$ + String key = entry.getKey(); + sb.append(key); + sb.append("=\""); //$NON-NLS-1$ + sb.append(getEscaped(String.valueOf(entry.getValue()))); + sb.append("\""); //$NON-NLS-1$ + } + sb.append(">"); //$NON-NLS-1$ + if (shouldTab) + printTabulation(); + if (newLine) + println(sb.toString()); + else + print(sb.toString()); + } + + public void startTag(String name, HashMap parameters) { + startTag(name, parameters, true); + } + + public void startTag(String name, HashMap parameters, boolean newLine) { + printTag(name, parameters, true, newLine); + tab++; + } + + private static void appendEscapedChar(StringBuilder buffer, char c) { + String replacement = getReplacement(c); + if (replacement != null) { + buffer.append('&'); + buffer.append(replacement); + buffer.append(';'); + } else { + buffer.append(c); + } + } + + public static String getEscaped(String s) { + StringBuilder result = new StringBuilder(s.length() + 10); + for (int i = 0; i < s.length(); ++i) + appendEscapedChar(result, s.charAt(i)); + return result.toString(); + } + + private static String getReplacement(char c) { + // Encode special XML characters into the equivalent character references. + // These five are defined by default for all XML documents. + switch (c) { + case '<' : + return "lt"; //$NON-NLS-1$ + case '>' : + return "gt"; //$NON-NLS-1$ + case '"' : + return "quot"; //$NON-NLS-1$ + case '\'' : + return "apos"; //$NON-NLS-1$ + case '&' : + return "amp"; //$NON-NLS-1$ + } + return null; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java new file mode 100644 index 0000000000..f62736c41d --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2006, 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; + +/** + * A description of the changes found in a delta + */ +public class ChangeDescription { + + private List addedRoots = new ArrayList<>(); + private List changedFiles = new ArrayList<>(); + private List closedProjects = new ArrayList<>(); + private List copiedRoots = new ArrayList<>(); + private List movedRoots = new ArrayList<>(); + private List removedRoots = new ArrayList<>(); + + private IResource createSourceResource(IResourceDelta delta) { + IPath sourcePath = delta.getMovedFromPath(); + IResource resource = delta.getResource(); + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); + switch (resource.getType()) { + case IResource.PROJECT : + return wsRoot.getProject(sourcePath.segment(0)); + case IResource.FOLDER : + return wsRoot.getFolder(sourcePath); + case IResource.FILE : + return wsRoot.getFile(sourcePath); + } + return null; + } + + private void ensureResourceCovered(IResource resource, List list) { + IPath path = resource.getFullPath(); + for (Iterator iter = list.iterator(); iter.hasNext();) { + IResource root = iter.next(); + if (root.getFullPath().isPrefixOf(path)) { + return; + } + } + list.add(resource); + } + + public IResource[] getRootResources() { + Set result = new HashSet<>(); + result.addAll(addedRoots); + result.addAll(changedFiles); + result.addAll(closedProjects); + result.addAll(copiedRoots); + result.addAll(movedRoots); + result.addAll(removedRoots); + return result.toArray(new IResource[result.size()]); + } + + private void handleAdded(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) { + handleMove(delta); + } else if ((delta.getFlags() & IResourceDelta.COPIED_FROM) != 0) { + handleCopy(delta); + } else { + ensureResourceCovered(delta.getResource(), addedRoots); + } + } + + private void handleChange(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.REPLACED) != 0) { + // A replace was added in place of a removed resource + handleAdded(delta); + } else if (delta.getResource().getType() == IResource.FILE) { + ensureResourceCovered(delta.getResource(), changedFiles); + } + } + + private void handleCopy(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.COPIED_FROM) != 0) { + IResource source = createSourceResource(delta); + ensureResourceCovered(source, copiedRoots); + } + } + + private void handleMove(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) { + movedRoots.add(delta.getResource()); + } else if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) { + IResource source = createSourceResource(delta); + ensureResourceCovered(source, movedRoots); + } + } + + private void handleRemoved(IResourceDelta delta) { + if ((delta.getFlags() & IResourceDelta.OPEN) != 0) { + closedProjects.add(delta.getResource()); + } else if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) { + handleMove(delta); + } else { + ensureResourceCovered(delta.getResource(), removedRoots); + } + } + + /** + * Record the change and return whether any child changes should be visited. + * @param delta the change + * @return whether any child changes should be visited + */ + public boolean recordChange(IResourceDelta delta) { + switch (delta.getKind()) { + case IResourceDelta.ADDED : + handleAdded(delta); + return true; // Need to traverse children to look for moves or other changes under added roots + case IResourceDelta.REMOVED : + handleRemoved(delta); + // No need to look for further changes under a remove (such as moves). + // Changes will be discovered in corresponding destination delta + return false; + case IResourceDelta.CHANGED : + handleChange(delta); + return true; + } + return true; + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java new file mode 100644 index 0000000000..7f5577f9a7 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.*; +import org.eclipse.core.expressions.*; +import org.eclipse.core.internal.resources.ResourceException; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +public class ModelProviderDescriptor implements IModelProviderDescriptor { + + private String id; + private String[] extendedModels; + private String label; + private ModelProvider provider; + private Expression enablementRule; + + private static EvaluationContext createEvaluationContext(Object element) { + EvaluationContext result = new EvaluationContext(null, element); + return result; + } + + public ModelProviderDescriptor(IExtension extension) throws CoreException { + readExtension(extension); + } + + private boolean convert(EvaluationResult eval) { + if (eval == EvaluationResult.FALSE) + return false; + return true; + } + + protected void fail(String reason) throws CoreException { + throw new ResourceException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, reason, null)); + } + + @Override + public String[] getExtendedModels() { + return extendedModels; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public IResource[] getMatchingResources(IResource[] resources) throws CoreException { + Set result = new HashSet<>(); + for (int i = 0; i < resources.length; i++) { + IResource resource = resources[i]; + EvaluationContext evalContext = createEvaluationContext(resource); + if (matches(evalContext)) { + result.add(resource); + } + } + return result.toArray(new IResource[result.size()]); + } + + @Override + public synchronized ModelProvider getModelProvider() throws CoreException { + if (provider == null) { + IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MODEL_PROVIDERS, id); + IConfigurationElement[] elements = extension.getConfigurationElements(); + for (int i = 0; i < elements.length; i++) { + IConfigurationElement element = elements[i]; + if (element.getName().equalsIgnoreCase("modelProvider")) { //$NON-NLS-1$ + try { + provider = (ModelProvider) element.createExecutableExtension("class"); //$NON-NLS-1$ + provider.init(this); + } catch (ClassCastException e) { + String message = NLS.bind(Messages.mapping_wrongType, id); + throw new CoreException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, Platform.PLUGIN_ERROR, message, e)); + } + } + } + } + return provider; + } + + public boolean matches(IEvaluationContext context) throws CoreException { + if (enablementRule == null) + return false; + return convert(enablementRule.evaluate(context)); + } + + /** + * Initialize this descriptor based on the provided extension point. + */ + protected void readExtension(IExtension extension) throws CoreException { + //read the extension + id = extension.getUniqueIdentifier(); + if (id == null) + fail(Messages.mapping_noIdentifier); + label = extension.getLabel(); + IConfigurationElement[] elements = extension.getConfigurationElements(); + int count = elements.length; + ArrayList extendsList = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + IConfigurationElement element = elements[i]; + String name = element.getName(); + if (name.equalsIgnoreCase("extends-model")) { //$NON-NLS-1$ + String attribute = element.getAttribute("id"); //$NON-NLS-1$ + if (attribute == null) + fail(NLS.bind(Messages.mapping_invalidDef, id)); + extendsList.add(attribute); + } else if (name.equalsIgnoreCase(ExpressionTagNames.ENABLEMENT)) { + enablementRule = ExpressionConverter.getDefault().perform(element); + } + } + extendedModels = extendsList.toArray(new String[extendsList.size()]); + } + + @Override + public ResourceTraversal[] getMatchingTraversals(ResourceTraversal[] traversals) throws CoreException { + List result = new ArrayList<>(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + if (getMatchingResources(traversal.getResources()).length > 0) { + result.add(traversal); + } + } + return result.toArray(new ResourceTraversal[result.size()]); + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java new file mode 100644 index 0000000000..01403a8e71 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.HashMap; +import java.util.Map; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.mapping.IModelProviderDescriptor; +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.runtime.*; + +public class ModelProviderManager { + + private static Map descriptors; + private static ModelProviderManager instance; + + public synchronized static ModelProviderManager getDefault() { + if (instance == null) { + instance = new ModelProviderManager(); + } + return instance; + } + + private void detectCycles() { + // TODO Auto-generated method stub + + } + + public IModelProviderDescriptor getDescriptor(String id) { + lazyInitialize(); + return descriptors.get(id); + } + + public IModelProviderDescriptor[] getDescriptors() { + lazyInitialize(); + return descriptors.values().toArray(new IModelProviderDescriptor[descriptors.size()]); + } + + public ModelProvider getModelProvider(String modelProviderId) throws CoreException { + IModelProviderDescriptor desc = getDescriptor(modelProviderId); + if (desc == null) + return null; + return desc.getModelProvider(); + } + + protected void lazyInitialize() { + if (descriptors != null) + return; + IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MODEL_PROVIDERS); + IExtension[] extensions = point.getExtensions(); + descriptors = new HashMap<>(extensions.length * 2 + 1); + for (int i = 0, imax = extensions.length; i < imax; i++) { + IModelProviderDescriptor desc = null; + try { + desc = new ModelProviderDescriptor(extensions[i]); + } catch (CoreException e) { + Policy.log(e); + } + if (desc != null) + descriptors.put(desc.getId(), desc); + } + //do cycle detection now so it only has to be done once + //cycle detection on a graph subset is a pain + detectCycles(); + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java new file mode 100644 index 0000000000..e3e3bc494f --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright (c) 2006, 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * Concrete implementation of IResourceDelta used for operation validation + */ +public final class ProposedResourceDelta extends PlatformObject implements IResourceDelta { + protected static int KIND_MASK = 0xFF; + + private HashMap children = new HashMap<>(8); + private IPath movedFromPath; + private IPath movedToPath; + private IResource resource; + private int status; + + public ProposedResourceDelta(IResource resource) { + this.resource = resource; + } + + @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 { + if (!visitor.visit(this)) + return; + for (Iterator iter = children.values().iterator(); iter.hasNext();) { + ProposedResourceDelta childDelta = iter.next(); + childDelta.accept(visitor, memberFlags); + } + } + + /** + * Adds a child delta to the list of children for this delta node. + * @param delta + */ + protected void add(ProposedResourceDelta delta) { + if (children.size() == 0 && status == 0) + setKind(IResourceDelta.CHANGED); + children.put(delta.getResource().getName(), delta); + } + + /** + * Adds the given flags to this delta. + * @param flags The flags to add + */ + protected void addFlags(int flags) { + //make sure the provided flags don't influence the kind + this.status |= (flags & ~KIND_MASK); + } + + @Override + public IResourceDelta findMember(IPath path) { + int segmentCount = path.segmentCount(); + if (segmentCount == 0) + return this; + + //iterate over the path and find matching child delta + ProposedResourceDelta current = this; + for (int i = 0; i < segmentCount; i++) { + current = current.children.get(path.segment(i)); + if (current == null) + return null; + } + return current; + } + + @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) { + List result = new ArrayList<>(); + for (Iterator iter = children.values().iterator(); iter.hasNext();) { + ProposedResourceDelta child = iter.next(); + if ((child.getKind() & kindMask) != 0) + result.add(child); + } + return result.toArray(new IResourceDelta[result.size()]); + } + + /** + * Returns the child delta corresponding to the given child resource name, + * or null. + */ + ProposedResourceDelta getChild(String name) { + return children.get(name); + } + + @Override + public int getFlags() { + return status & ~KIND_MASK; + } + + @Override + public IPath getFullPath() { + return getResource().getFullPath(); + } + + @Override + public int getKind() { + return status & KIND_MASK; + } + + @Override + public IMarkerDelta[] getMarkerDeltas() { + return new IMarkerDelta[0]; + } + + @Override + public IPath getMovedFromPath() { + return movedFromPath; + } + + @Override + public IPath getMovedToPath() { + return movedToPath; + } + + @Override + public IPath getProjectRelativePath() { + return getResource().getProjectRelativePath(); + } + + @Override + public IResource getResource() { + return resource; + } + + public void setFlags(int flags) { + status = getKind() | (flags & ~KIND_MASK); + } + + protected void setKind(int kind) { + status = getFlags() | (kind & KIND_MASK); + } + + protected void setMovedFromPath(IPath path) { + movedFromPath = path; + } + + protected void setMovedToPath(IPath path) { + movedToPath = path; + } + + /** + * For debugging purposes only. + */ + @Override + public String toString() { + return "ProposedDelta(" + resource + ')'; //$NON-NLS-1$ + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java new file mode 100644 index 0000000000..03c0ee77f1 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * 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.resources.mapping; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.runtime.IAdapterFactory; + +/** + * Adapter factory converting IResource to ResourceMapping + * + * @since 3.1 + */ +public class ResourceAdapterFactory implements IAdapterFactory { + @Override + @SuppressWarnings("unchecked") + public T getAdapter(Object adaptableObject, Class adapterType) { + if (adapterType == ResourceMapping.class && adaptableObject instanceof IResource) { + return (T) new SimpleResourceMapping((IResource) adaptableObject); + } + return null; + } + + @Override + public Class[] getAdapterList() { + return new Class[] {ResourceMapping.class}; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java new file mode 100644 index 0000000000..bbdfa5ee42 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java @@ -0,0 +1,240 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.mapping; + +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; + +/** + * Factory for creating a resource delta that describes a proposed change. + */ +public class ResourceChangeDescriptionFactory implements IResourceChangeDescriptionFactory { + private ProposedResourceDelta root = new ProposedResourceDelta(ResourcesPlugin.getWorkspace().getRoot()); + + /** + * Creates and a delta representing a deleted resource, and adds it to the provided + * parent delta. + * @param parentDelta The parent of the deletion delta to create + * @param resource The deleted resource to create a delta for + */ + private ProposedResourceDelta buildDeleteDelta(ProposedResourceDelta parentDelta, IResource resource) { + //start with the existing delta for this resource, if any, to preserve other flags + ProposedResourceDelta delta = parentDelta.getChild(resource.getName()); + if (delta == null) { + delta = new ProposedResourceDelta(resource); + parentDelta.add(delta); + } + delta.setKind(IResourceDelta.REMOVED); + if (resource.getType() == IResource.FILE) + return delta; + //recurse to build deletion deltas for children + try { + IResource[] members = ((IContainer) resource).members(); + int childCount = members.length; + if (childCount > 0) { + ProposedResourceDelta[] childDeltas = new ProposedResourceDelta[childCount]; + for (int i = 0; i < childCount; i++) + childDeltas[i] = buildDeleteDelta(delta, members[i]); + } + } catch (CoreException e) { + //don't need to create deletion deltas for children of inaccessible resources + } + return delta; + } + + @Override + public void change(IFile file) { + ProposedResourceDelta delta = getDelta(file); + if (delta.getKind() == 0) + delta.setKind(IResourceDelta.CHANGED); + //the CONTENT flag only applies to the changed and moved from cases + if (delta.getKind() == IResourceDelta.CHANGED || (delta.getFlags() & IResourceDelta.MOVED_FROM) != 0 || (delta.getFlags() & IResourceDelta.COPIED_FROM) != 0) + delta.addFlags(IResourceDelta.CONTENT); + } + + @Override + public void close(IProject project) { + delete(project); + ProposedResourceDelta delta = getDelta(project); + delta.addFlags(IResourceDelta.OPEN); + } + + @Override + public void copy(IResource resource, IPath destination) { + moveOrCopyDeep(resource, destination, false /* copy */); + } + + @Override + public void create(IResource resource) { + getDelta(resource).setKind(IResourceDelta.ADDED); + } + + @Override + public void delete(IResource resource) { + if (resource.getType() == IResource.ROOT) { + //the root itself cannot be deleted, so create deletions for each project + IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN); + for (int i = 0; i < projects.length; i++) + buildDeleteDelta(root, projects[i]); + } else { + buildDeleteDelta(getDelta(resource.getParent()), resource); + } + } + + private void fail(CoreException e) { + Policy.log(e.getStatus().getSeverity(), "An internal error occurred while accumulating a change description.", e); //$NON-NLS-1$ + } + + @Override + public IResourceDelta getDelta() { + return root; + } + + ProposedResourceDelta getDelta(IResource resource) { + ProposedResourceDelta delta = (ProposedResourceDelta) root.findMember(resource.getFullPath()); + if (delta != null) { + return delta; + } + ProposedResourceDelta parent = getDelta(resource.getParent()); + delta = new ProposedResourceDelta(resource); + parent.add(delta); + return delta; + } + + /* + * Return the resource at the destination path that corresponds to the source resource + * @param source the source resource + * @param sourcePrefix the path of the root of the move or copy + * @param destinationPrefix the path of the destination the root was copied to + * @return the destination resource + */ + protected IResource getDestinationResource(IResource source, IPath sourcePrefix, IPath destinationPrefix) { + IPath relativePath = source.getFullPath().removeFirstSegments(sourcePrefix.segmentCount()); + IPath destinationPath = destinationPrefix.append(relativePath); + IResource destination; + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); + switch (source.getType()) { + case IResource.FILE : + destination = wsRoot.getFile(destinationPath); + break; + case IResource.FOLDER : + destination = wsRoot.getFolder(destinationPath); + break; + case IResource.PROJECT : + destination = wsRoot.getProject(destinationPath.segment(0)); + break; + default : + // Shouldn't happen + destination = null; + } + return destination; + } + + @Override + public void move(IResource resource, IPath destination) { + moveOrCopyDeep(resource, destination, true /* move */); + } + + /** + * Builds the delta representing a single resource being moved or copied. + * + * @param resource The resource being moved + * @param sourcePrefix The root of the sub-tree being moved + * @param destinationPrefix The root of the destination sub-tree + * @param move true for a move, false for a copy + * @return Whether to move or copy the child + */ + boolean moveOrCopy(IResource resource, final IPath sourcePrefix, final IPath destinationPrefix, final boolean move) { + ProposedResourceDelta sourceDelta = getDelta(resource); + if (sourceDelta.getKind() == IResourceDelta.REMOVED) { + // There is already a removed delta here so there + // is nothing to move/copy + return false; + } + IResource destinationResource = getDestinationResource(resource, sourcePrefix, destinationPrefix); + ProposedResourceDelta destinationDelta = getDelta(destinationResource); + if ((destinationDelta.getKind() & (IResourceDelta.ADDED | IResourceDelta.CHANGED)) > 0) { + // There is already a resource at the destination + // TODO: What do we do + return false; + } + // First, create the delta for the source + IPath fromPath = resource.getFullPath(); + boolean wasAdded = false; + final int sourceFlags = sourceDelta.getFlags(); + if (move) { + // We transfer the source flags to the destination + if (sourceDelta.getKind() == IResourceDelta.ADDED) { + if ((sourceFlags & IResourceDelta.MOVED_FROM) != 0) { + // The resource was moved from somewhere else so + // we need to transfer the path to the new location + fromPath = sourceDelta.getMovedFromPath(); + sourceDelta.setMovedFromPath(null); + } + // The source was added and then moved so we'll + // make it an add at the destination + sourceDelta.setKind(0); + wasAdded = true; + } else { + // We reset the status to be a remove/move_to + sourceDelta.setKind(IResourceDelta.REMOVED); + sourceDelta.setFlags(IResourceDelta.MOVED_TO); + sourceDelta.setMovedToPath(destinationPrefix.append(fromPath.removeFirstSegments(sourcePrefix.segmentCount()))); + } + } + // Next, create the delta for the destination + if (destinationDelta.getKind() == IResourceDelta.REMOVED) { + // The destination was removed and is being re-added + destinationDelta.setKind(IResourceDelta.CHANGED); + destinationDelta.addFlags(IResourceDelta.REPLACED); + } else { + destinationDelta.setKind(IResourceDelta.ADDED); + } + if (!wasAdded || !fromPath.equals(resource.getFullPath())) { + // The source wasn't added so it is a move/copy + destinationDelta.addFlags(move ? IResourceDelta.MOVED_FROM : IResourceDelta.COPIED_FROM); + destinationDelta.setMovedFromPath(fromPath); + // Apply the source flags + if (move) + destinationDelta.addFlags(sourceFlags); + } + + return true; + } + + /** + * Helper method that generate a move or copy delta for a sub-tree + * of resources being moved or copied. + */ + private void moveOrCopyDeep(IResource resource, IPath destination, final boolean move) { + final IPath sourcePrefix = resource.getFullPath(); + final IPath destinationPrefix = destination; + try { + //build delta for the entire sub-tree if available + if (resource.isAccessible()) { + resource.accept(new IResourceVisitor() { + @Override + public boolean visit(IResource child) { + return moveOrCopy(child, sourcePrefix, destinationPrefix, move); + } + }); + } else { + //just build a delta for the single resource + moveOrCopy(resource, sourcePrefix, destination, move); + } + } catch (CoreException e) { + fail(e); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java new file mode 100644 index 0000000000..771e9f56b7 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.resources.mapping; + +import java.util.*; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A simple model provider that represents the resource model itself. + * + * @since 3.2 + */ +public final class ResourceModelProvider extends ModelProvider { + + @Override + public ResourceMapping[] getMappings(IResource resource, ResourceMappingContext context, IProgressMonitor monitor) { + return new ResourceMapping[] {new SimpleResourceMapping(resource)}; + } + + @Override + public ResourceMapping[] getMappings(ResourceTraversal[] traversals, ResourceMappingContext context, IProgressMonitor monitor) { + Set result = new HashSet<>(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + IResource[] resources = traversal.getResources(); + int depth = traversal.getDepth(); + for (int j = 0; j < resources.length; j++) { + IResource resource = resources[j]; + switch (depth) { + case IResource.DEPTH_INFINITE : + result.add(resource); + break; + case IResource.DEPTH_ONE : + if (resource.getType() == IResource.FILE) { + result.add(resource); + } else { + result.add(new ShallowContainer((IContainer) resource)); + } + break; + case IResource.DEPTH_ZERO : + if (resource.getType() == IResource.FILE) + result.add(resource); + break; + } + } + } + ResourceMapping[] mappings = new ResourceMapping[result.size()]; + int i = 0; + for (Iterator iter = result.iterator(); iter.hasNext();) { + Object element = iter.next(); + if (element instanceof IResource) { + mappings[i++] = new SimpleResourceMapping((IResource) element); + } else { + mappings[i++] = new ShallowResourceMapping((ShallowContainer) element); + } + } + return mappings; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java new file mode 100644 index 0000000000..f26d56bc18 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.mapping; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.PlatformObject; + +/** + * A special model object used to represent shallow folders + */ +public class ShallowContainer extends PlatformObject { + + private IContainer container; + + public ShallowContainer(IContainer container) { + this.container = container; + } + + public IContainer getResource() { + return container; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj instanceof ShallowContainer) { + ShallowContainer other = (ShallowContainer) obj; + return other.getResource().equals(getResource()); + } + return false; + } + + @Override + public int hashCode() { + return getResource().hashCode(); + } + + @Override + @SuppressWarnings("unchecked") + public T getAdapter(Class adapter) { + if (adapter == IResource.class || adapter == IContainer.class) + return (T) container; + return super.getAdapter(adapter); + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java new file mode 100644 index 0000000000..54421f0f3f --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.mapping; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A resource mapping for a shallow container. + */ +public class ShallowResourceMapping extends ResourceMapping { + + private final ShallowContainer container; + + public ShallowResourceMapping(ShallowContainer container) { + this.container = container; + } + + @Override + public Object getModelObject() { + return container; + } + + @Override + public String getModelProviderId() { + return ModelProvider.RESOURCE_MODEL_PROVIDER_ID; + } + + @Override + public IProject[] getProjects() { + return new IProject[] { container.getResource().getProject() }; + } + + @Override + public ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) { + return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { container.getResource() }, IResource.DEPTH_ONE, IResource.NONE)}; + } + + @Override + public boolean contains(ResourceMapping mapping) { + if (mapping.getModelProviderId().equals(this.getModelProviderId())) { + Object object = mapping.getModelObject(); + IResource resource = container.getResource(); + // A shallow mapping only contains direct file children or equal shallow containers + if (object instanceof ShallowContainer) { + ShallowContainer sc = (ShallowContainer) object; + return sc.getResource().equals(resource); + } + if (object instanceof IResource) { + IResource other = (IResource) object; + return other.getType() == IResource.FILE + && resource.getFullPath().equals(other.getFullPath().removeLastSegments(1)); + } + } + return false; + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java new file mode 100644 index 0000000000..94fa38b414 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * 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.resources.mapping; + +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.mapping.*; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A simple resource mapping for converting IResource to ResourceMapping. + * It uses the resource as the model object and traverses deeply. + * + * @since 3.1 + */ +public class SimpleResourceMapping extends ResourceMapping { + private final IResource resource; + + public SimpleResourceMapping(IResource resource) { + this.resource = resource; + } + + @Override + public boolean contains(ResourceMapping mapping) { + if (mapping.getModelProviderId().equals(this.getModelProviderId())) { + Object object = mapping.getModelObject(); + if (object instanceof IResource) { + IResource other = (IResource) object; + return resource.getFullPath().isPrefixOf(other.getFullPath()); + } + if (object instanceof ShallowContainer) { + ShallowContainer sc = (ShallowContainer) object; + IResource other = sc.getResource(); + return resource.getFullPath().isPrefixOf(other.getFullPath()); + } + } + return false; + } + + @Override + public Object getModelObject() { + return resource; + } + + @Override + public String getModelProviderId() { + return ModelProvider.RESOURCE_MODEL_PROVIDER_ID; + } + + @Override + public IProject[] getProjects() { + if (resource.getType() == IResource.ROOT) + return ((IWorkspaceRoot)resource).getProjects(); + return new IProject[] {resource.getProject()}; + } + + @Override + public ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) { + if (resource.getType() == IResource.ROOT) { + return new ResourceTraversal[] {new ResourceTraversal(((IWorkspaceRoot)resource).getProjects(), IResource.DEPTH_INFINITE, IResource.NONE)}; + } + return new ResourceTraversal[] {new ResourceTraversal(new IResource[] {resource}, IResource.DEPTH_INFINITE, IResource.NONE)}; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java new file mode 100644 index 0000000000..6c2a9646d3 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/EclipseHomeProjectVariable.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import java.net.URISyntaxException; +import java.net.URL; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.URIUtil; + +/** + * ECLIPSE_HOME project variable, pointing to the location of the eclipse install directory. + * + */ +public class EclipseHomeProjectVariable extends PathVariableResolver { + + public static String NAME = "ECLIPSE_HOME"; //$NON-NLS-1$ + + public EclipseHomeProjectVariable() { + // nothing to do. + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + URL installURL = Platform.getInstallLocation().getURL(); + try { + return URIUtil.toURI(installURL).toASCIIString(); + } catch (URISyntaxException e) { + return null; + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java new file mode 100644 index 0000000000..6ebb96a180 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ParentVariableResolver.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import java.net.URI; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * Path Variable representing the parent directory of the variable provided + * in argument, following the syntax: + * + * "${PARENT-COUNT-MyVariable}" + * + */ +public class ParentVariableResolver extends PathVariableResolver { + + final public static String NAME = "PARENT"; //$NON-NLS-1$ + + public ParentVariableResolver() { + // nothing + } + + @Override + public String getValue(String variable, IResource resource) { + int index = variable.indexOf('-'); + if (index == -1 || index == (variable.length() -1)) + return null; + + String countRemaining = variable.substring(index + 1); + index = countRemaining.indexOf('-'); + if (index == -1 || index == (variable.length() -1)) + return null; + + String countString = countRemaining.substring(0, index); + int count = 0; + try { + count = Integer.parseInt(countString); + if (count < 0) + return null; + }catch (NumberFormatException e) { + return null; + } + String argument = countRemaining.substring(index + 1); + + URI value = resource.getPathVariableManager().getURIValue(argument); + if (value == null) + return null; + value = resource.getPathVariableManager().resolveURI(value); + value = URIUtil.toURI(URIUtil.toPath(value).removeLastSegments(count)); + + return value.toASCIIString(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java new file mode 100644 index 0000000000..a9c232fdb4 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/ProjectLocationVariableResolver.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * + */ +public class ProjectLocationVariableResolver extends PathVariableResolver { + + public static String NAME = "PROJECT_LOC"; //$NON-NLS-1$ + + public ProjectLocationVariableResolver() { + // nothing + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + if (resource.getProject().getLocationURI() != null) + return resource.getProject().getLocationURI().toASCIIString(); + return null; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java new file mode 100644 index 0000000000..81cefeb12c --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceLocationVariableResolver.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * + */ +public class WorkspaceLocationVariableResolver extends PathVariableResolver { + + public static String NAME = "WORKSPACE_LOC"; //$NON-NLS-1$ + + public WorkspaceLocationVariableResolver() { + // nothing to do + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + return resource.getWorkspace().getRoot().getLocationURI().toASCIIString(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java new file mode 100644 index 0000000000..71c6bbf176 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/projectvariables/WorkspaceParentLocationVariableResolver.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.resources.projectvariables; + +import java.net.URI; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.variableresolvers.PathVariableResolver; + +/** + * Returns the location of the parent resource + */ +public class WorkspaceParentLocationVariableResolver extends PathVariableResolver { + + public static String NAME = "PARENT_LOC"; //$NON-NLS-1$ + + public WorkspaceParentLocationVariableResolver() { + // nothing + } + + @Override + public String[] getVariableNames(String variable, IResource resource) { + return new String[] {NAME}; + } + + @Override + public String getValue(String variable, IResource resource) { + IContainer parent = resource.getParent(); + if (parent != null) { + URI locationURI = parent.getLocationURI(); + if (locationURI != null) + return locationURI.toASCIIString(); + } + return null; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java new file mode 100644 index 0000000000..84554589b6 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2002, 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 - Initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.resources.refresh.win32; + +import java.io.*; + +/** + * Performs character to byte conversion for passing strings to native win32 + * methods. + */ +public class Convert { + + /* + * Obtains the default encoding on this platform + */ + private static String defaultEncoding= + new InputStreamReader(new ByteArrayInputStream(new byte[0])).getEncoding(); + + /** + * Converts the given String to bytes using the platforms default + * encoding. + * + * @param target The String to be converted, can not be null. + * @return byte[] The resulting bytes, or null. + */ + /** + * Calling String.getBytes() creates a new encoding object and other garbage. + * This can be avoided by calling String.getBytes(String encoding) instead. + */ + public static byte[] toPlatformBytes(String target) { + if (defaultEncoding == null) + return target.getBytes(); + // try to use the default encoding + try { + return target.getBytes(defaultEncoding); + } catch (UnsupportedEncodingException e) { + // null the default encoding so we don't try it again + defaultEncoding = null; + return target.getBytes(); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java new file mode 100644 index 0000000000..26bf4aa974 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java @@ -0,0 +1,631 @@ +/******************************************************************************* + * Copyright (c) 2002, 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 - Initial API and implementation + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + * Mikael Barbero (Eclipse Foundation) - 286681 handle WAIT_ABANDONED_0 return value + *******************************************************************************/ +package org.eclipse.core.internal.resources.refresh.win32; + +import java.io.File; +import java.util.*; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.refresh.IRefreshMonitor; +import org.eclipse.core.resources.refresh.IRefreshResult; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.Bundle; + +/** + * A monitor that works on Win32 platforms. Provides simple notification of + * entire trees by reporting that the root of the tree has changed to depth + * DEPTH_INFINITE. + */ +class Win32Monitor extends Job implements IRefreshMonitor { + /** + * The delay between invocations of the refresh job. + */ + private static final long RESCHEDULE_DELAY = 3000; + /** + * The time to wait on blocking call to native refresh hook. + */ + private static final int WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT = 1000; + private static final String DEBUG_PREFIX = "Win32RefreshMonitor: "; //$NON-NLS-1$ + + /** + * A ChainedHandle is a linked list of handles. + */ + protected abstract class ChainedHandle extends Handle { + private ChainedHandle next; + private ChainedHandle previous; + + public abstract boolean exists(); + + public ChainedHandle getNext() { + return next; + } + + public ChainedHandle getPrevious() { + return previous; + } + + public void setNext(ChainedHandle next) { + this.next = next; + } + + public void setPrevious(ChainedHandle previous) { + this.previous = previous; + } + } + + protected class FileHandle extends ChainedHandle { + private File file; + + public FileHandle(File file) { + this.file = file; + } + + @Override + public boolean exists() { + return file.exists(); + } + + @Override + public void handleNotification() { + if (!isOpen()) + return; + ChainedHandle next = getNext(); + if (next != null) { + if (next.isOpen()) { + if (!next.exists()) { + if (next instanceof LinkedResourceHandle) { + next.close(); + LinkedResourceHandle linkedResourceHandle = (LinkedResourceHandle) next; + linkedResourceHandle.postRefreshRequest(); + } else { + next.close(); + } + ChainedHandle previous = getPrevious(); + if (previous != null) + previous.open(); + } + } else { + next.open(); + if (next.isOpen()) { + Handle previous = getPrevious(); + previous.close(); + if (next instanceof LinkedResourceHandle) + ((LinkedResourceHandle) next).postRefreshRequest(); + } + } + } + findNextChange(); + } + + @Override + public void open() { + if (!isOpen()) { + Handle next = getNext(); + if (next != null && next.isOpen()) { + openHandleOn(file); + } else { + if (exists()) { + openHandleOn(file); + } + Handle previous = getPrevious(); + if (previous != null) { + previous.open(); + } + } + } + } + + @Override + public String toString() { + return file.toString(); + } + } + + protected abstract class Handle { + protected long handleValue; + + public Handle() { + handleValue = Win32Natives.INVALID_HANDLE_VALUE; + } + + public void close() { + if (isOpen()) { + if (!Win32Natives.FindCloseChangeNotification(handleValue)) { + int error = Win32Natives.GetLastError(); + if (error != Win32Natives.ERROR_INVALID_HANDLE) + addException(NLS.bind(Messages.WM_errCloseHandle, Integer.toString(error))); + } + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "removed handle: " + handleValue); //$NON-NLS-1$ + handleValue = Win32Natives.INVALID_HANDLE_VALUE; + } + } + + private long createHandleValue(String path, boolean monitorSubtree, int flags) { + long handle = Win32Natives.FindFirstChangeNotification(path, monitorSubtree, flags); + if (handle == Win32Natives.INVALID_HANDLE_VALUE) { + int error = Win32Natives.GetLastError(); + addException(NLS.bind(Messages.WM_errCreateHandle, path, Integer.toString(error))); + } + return handle; + } + + public void destroy() { + close(); + } + + protected void findNextChange() { + if (!Win32Natives.FindNextChangeNotification(handleValue)) { + int error = Win32Natives.GetLastError(); + if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) { + addException(NLS.bind(Messages.WM_errFindChange, Integer.toString(error))); + } + removeHandle(this); + } + } + + public long getHandleValue() { + return handleValue; + } + + public abstract void handleNotification(); + + public boolean isOpen() { + return handleValue != Win32Natives.INVALID_HANDLE_VALUE; + } + + public abstract void open(); + + protected void openHandleOn(File file) { + openHandleOn(file.getAbsolutePath(), false); + } + + protected void openHandleOn(IResource resource) { + IPath location = resource.getLocation(); + if (location != null) { + openHandleOn(location.toOSString(), true); + } + } + + private void openHandleOn(String path, boolean subtree) { + setHandleValue(createHandleValue(path, subtree, Win32Natives.FILE_NOTIFY_CHANGE_FILE_NAME | Win32Natives.FILE_NOTIFY_CHANGE_DIR_NAME | Win32Natives.FILE_NOTIFY_CHANGE_LAST_WRITE | Win32Natives.FILE_NOTIFY_CHANGE_SIZE)); + if (isOpen()) { + fHandleValueToHandle.put(getHandleValue(), this); + setHandleValueArrays(createHandleArrays()); + } else { + close(); + } + } + + protected void postRefreshRequest(IResource resource) { + //native callback occurs even if resource was changed within workspace + if (!resource.isSynchronized(IResource.DEPTH_INFINITE)) + refreshResult.refresh(resource); + } + + public void setHandleValue(long handleValue) { + this.handleValue = handleValue; + } + } + + protected class LinkedResourceHandle extends ChainedHandle { + private List fileHandleChain; + private IResource resource; + + /** + * @param resource + */ + public LinkedResourceHandle(IResource resource) { + this.resource = resource; + createFileHandleChain(); + } + + protected void createFileHandleChain() { + fileHandleChain = new ArrayList<>(1); + File file = new File(resource.getLocation().toOSString()); + file = file.getParentFile(); + while (file != null) { + fileHandleChain.add(0, new FileHandle(file)); + file = file.getParentFile(); + } + int size = fileHandleChain.size(); + for (int i = 0; i < size; i++) { + ChainedHandle handle = fileHandleChain.get(i); + handle.setPrevious((i > 0) ? fileHandleChain.get(i - 1) : null); + handle.setNext((i + 1 < size) ? fileHandleChain.get(i + 1) : this); + } + setPrevious((size > 0) ? fileHandleChain.get(size - 1) : null); + } + + @Override + public void destroy() { + super.destroy(); + for (Iterator i = fileHandleChain.iterator(); i.hasNext();) { + Handle handle = i.next(); + handle.destroy(); + } + } + + @Override + public boolean exists() { + IPath location = resource.getLocation(); + return location == null ? false : location.toFile().exists(); + } + + @Override + public void handleNotification() { + if (isOpen()) { + postRefreshRequest(resource); + findNextChange(); + } + } + + @Override + public void open() { + if (!isOpen()) { + if (exists()) { + openHandleOn(resource); + } + FileHandle handle = (FileHandle) getPrevious(); + if (handle != null && !handle.isOpen()) { + handle.open(); + } + } + } + + public void postRefreshRequest() { + postRefreshRequest(resource); + } + + @Override + public String toString() { + return resource.toString(); + } + } + + protected class ResourceHandle extends Handle { + private IResource resource; + + public ResourceHandle(IResource resource) { + super(); + this.resource = resource; + } + + public IResource getResource() { + return resource; + } + + @Override + public void handleNotification() { + if (isOpen()) { + postRefreshRequest(resource); + findNextChange(); + } + } + + @Override + public void open() { + if (!isOpen()) { + openHandleOn(resource); + } + } + + @Override + public String toString() { + return resource.toString(); + } + } + + /** + * Any errors that have occurred + */ + protected MultiStatus errors; + /** + * Arrays of handles, split evenly when the number of handles is larger + * than Win32Natives.MAXIMUM_WAIT_OBJECTS + */ + protected long[][] fHandleValueArrays; + /** + * Mapping of handles (java.lang.Long) to absolute paths + * (java.lang.String). + */ + protected Map fHandleValueToHandle; + protected IRefreshResult refreshResult; + + /* + * Creates a new monitor. @param result A result that will receive refresh + * callbacks and error notifications + */ + public Win32Monitor(IRefreshResult result) { + super(Messages.WM_jobName); + this.refreshResult = result; + setPriority(Job.DECORATE); + setSystem(true); + fHandleValueToHandle = new HashMap<>(1); + setHandleValueArrays(createHandleArrays()); + } + + /** + * Logs an exception + */ + protected synchronized void addException(String message) { + if (errors == null) { + String msg = Messages.WM_errors; + errors = new MultiStatus(ResourcesPlugin.PI_RESOURCES, 1, msg, null); + } + errors.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, message, null)); + } + + /* + * Splits the given array into arrays of length no greater than max + * . The lengths of the sub arrays differ in size by no more than + * one element.

                  Examples:

                  • If an array of size 11 is split + * with a max of 4, the resulting arrays are of size 4, 4, and 3.
                  • + *
                  • If an array of size 18 is split with a max of 5, the resulting + * arrays are of size 5, 5, 4, and 4.
                  + */ + private long[][] balancedSplit(final long[] array, final int max) { + int elementCount = array.length; + // want to handle [1, max] rather than [0, max) + int subArrayCount = ((elementCount - 1) / max) + 1; + int subArrayBaseLength = elementCount / subArrayCount; + int overflow = elementCount % subArrayCount; + long[][] result = new long[subArrayCount][]; + int count = 0; + for (int i = 0; i < subArrayCount; i++) { + int subArrayLength = subArrayBaseLength + (overflow-- > 0 ? 1 : 0); + long[] subArray = new long[subArrayLength]; + for (int j = 0; j < subArrayLength; j++) { + subArray[j] = array[count++]; + } + result[i] = subArray; + } + return result; + } + + private Handle createHandle(IResource resource) { + if (resource.isLinked()) + return new LinkedResourceHandle(resource); + return new ResourceHandle(resource); + } + + /* + * Since the Win32Natives.WaitForMultipleObjects(...) method cannot accept + * more than a certain number of objects, we are forced to split the array + * of objects to monitor and monitor each one individually.

                  This method + * splits the list of handles into arrays no larger than + * Win32Natives.MAXIMUM_WAIT_OBJECTS. The arrays are balanced so that they + * differ in size by no more than one element. + */ + protected long[][] createHandleArrays() { + long[] handles; + // synchronized: in order to protect the map during iteration + synchronized (fHandleValueToHandle) { + Set keys = fHandleValueToHandle.keySet(); + int size = keys.size(); + if (size == 0) { + return new long[0][0]; + } + handles = new long[size]; + int count = 0; + for (Iterator i = keys.iterator(); i.hasNext();) { + handles[count++] = i.next().longValue(); + } + } + return balancedSplit(handles, Win32Natives.MAXIMUM_WAIT_OBJECTS); + } + + private Handle getHandle(IResource resource) { + if (resource == null) { + return null; + } + // synchronized: in order to protect the map during iteration + synchronized (fHandleValueToHandle) { + for (Iterator i = fHandleValueToHandle.values().iterator(); i.hasNext();) { + Handle handle = i.next(); + if (handle instanceof ResourceHandle) { + ResourceHandle resourceHandle = (ResourceHandle) handle; + if (resourceHandle.getResource().equals(resource)) { + return handle; + } + } + } + } + return null; + } + + /* + * Answers arrays of handles. The handles are split evenly when the number + * of handles becomes larger than Win32Natives.MAXIMUM_WAIT_OBJECTS. + * @return long[][] + */ + private long[][] getHandleValueArrays() { + return fHandleValueArrays; + } + + /** + * Adds a resource to be monitored by this native monitor + */ + public boolean monitor(IResource resource) { + IPath location = resource.getLocation(); + if (location == null) { + // cannot monitor remotely managed containers + return false; + } + Handle handle = createHandle(resource); + // synchronized: handle creation must be atomic + synchronized (this) { + handle.open(); + } + if (!handle.isOpen()) { + //ignore errors if we can't even create a handle on the resource + //it will fall back to polling anyway + errors = null; + return false; + } + //make sure the job is running + schedule(RESCHEDULE_DELAY); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + " added monitor for: " + resource); //$NON-NLS-1$ + return true; + } + + /** + * Removes the handle from the fHandleValueToHandle map. + * + * @param handle + * a handle, not null + */ + protected void removeHandle(Handle handle) { + List handles = new ArrayList<>(1); + handles.add(handle); + removeHandles(handles); + } + + /** + * Removes all of the handles in the given collection from the fHandleValueToHandle + * map. If collections from the fHandleValueToHandle map are + * used, copy them before passing them in as this method modifies the + * fHandleValueToHandle map. + * + * @param handles + * a collection of handles, not null + */ + private void removeHandles(Collection handles) { + // synchronized: protect the array, removal must be atomic + synchronized (this) { + for (Iterator i = handles.iterator(); i.hasNext();) { + Handle handle = i.next(); + fHandleValueToHandle.remove(handle.getHandleValue()); + handle.destroy(); + } + setHandleValueArrays(createHandleArrays()); + } + } + + /* + * @see java.lang.Runnable#run() + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + long start = -System.currentTimeMillis(); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "job started."); //$NON-NLS-1$ + try { + long[][] handleArrays = getHandleValueArrays(); + monitor.beginTask(Messages.WM_beginTask, handleArrays.length); + // If changes occur to the list of handles, + // ignore them until the next time through the loop. + for (int i = 0, length = handleArrays.length; i < length; i++) { + if (monitor.isCanceled()) + return Status.CANCEL_STATUS; + waitForNotification(handleArrays[i]); + monitor.worked(1); + } + } finally { + monitor.done(); + start += System.currentTimeMillis(); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "job finished in: " + start + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + } + //always reschedule the job - so it will come back after errors or cancelation + long delay = Math.max(RESCHEDULE_DELAY, start); + if (Policy.DEBUG_AUTO_REFRESH) + Policy.debug(DEBUG_PREFIX + "rescheduling in: " + delay / 1000 + " seconds"); //$NON-NLS-1$ //$NON-NLS-2$ + final Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES); + //if the bundle is null then the framework has shutdown - just bail out completely (bug 98219) + if (bundle == null) + return Status.OK_STATUS; + //don't reschedule the job if the resources plugin has been shut down + if (bundle.getState() == Bundle.ACTIVE) + schedule(delay); + MultiStatus result = errors; + errors = null; + //just log native refresh failures + if (result != null && !result.isOK()) + ResourcesPlugin.getPlugin().getLog().log(result); + return Status.OK_STATUS; + } + + protected void setHandleValueArrays(long[][] arrays) { + fHandleValueArrays = arrays; + } + + @Override + public boolean shouldRun() { + return !fHandleValueToHandle.isEmpty(); + } + + @Override + public void unmonitor(IResource resource) { + if (resource == null) { + // resource == null means stop monitoring all resources + synchronized (fHandleValueToHandle) { + removeHandles(new ArrayList<>(fHandleValueToHandle.values())); + } + } else { + Handle handle = getHandle(resource); + if (handle != null) + removeHandle(handle); + } + //stop the job if there are no more handles + if (fHandleValueToHandle.isEmpty()) + cancel(); + } + + /** + * Performs the native call to wait for notification on one of the given + * handles. + * + * @param handleValues + * an array of handles, it must contain no duplicates. + */ + private void waitForNotification(long[] handleValues) { + int handleCount = handleValues.length; + int index = Win32Natives.WaitForMultipleObjects(handleCount, handleValues, false, WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT); + if (index == Win32Natives.WAIT_TIMEOUT) { + // nothing happened. + return; + } + if (index == Win32Natives.WAIT_FAILED) { + // we ran into a problem + int error = Win32Natives.GetLastError(); + if (error != Win32Natives.ERROR_INVALID_HANDLE && error != Win32Natives.ERROR_SUCCESS) { + addException(NLS.bind(Messages.WM_nativeErr, Integer.toString(error))); + refreshResult.monitorFailed(this, null); + } + return; + } + if (index >= Win32Natives.WAIT_ABANDONED_0) { + // abandoned mutex object that satisfied the wait + // WaitForMultipleObjects returns WAIT_ABANDONED_0 + index + index -= Win32Natives.WAIT_ABANDONED_0; + Handle handle = fHandleValueToHandle.get(handleValues[index]); + addException(NLS.bind(Messages.WM_mutexAbandoned, handle)); + refreshResult.monitorFailed(this, null); + return; + } + // a change occurred + // WaitForMultipleObjects returns WAIT_OBJECT_0 + index + index -= Win32Natives.WAIT_OBJECT_0; + Handle handle = fHandleValueToHandle.get(handleValues[index]); + if (handle != null) + handle.handleNotification(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java new file mode 100644 index 0000000000..b868b27dc3 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java @@ -0,0 +1,361 @@ +/******************************************************************************* + * Copyright (c) 2002, 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 - Initial API and implementation + * Mikael Barbero (Eclipse Foundation) - 286681 handle WAIT_ABANDONED_0 return value + *******************************************************************************/ +package org.eclipse.core.internal.resources.refresh.win32; + + +/** + * Hooks for native methods involved with win32 auto-refresh callbacks. + */ +public class Win32Natives { + /* general purpose */ + /** + * A general use constant expressing the value of an + * invalid handle. + */ + public static final long INVALID_HANDLE_VALUE; + /** + * An error constant which indicates that the previous function + * succeeded. + */ + public static final int ERROR_SUCCESS; + /** + * An error constant which indicates that a handle is or has become + * invalid. + */ + public static final int ERROR_INVALID_HANDLE; + /** + * The combination of all of the error constants. + */ + public static int FILE_NOTIFY_ALL; + /** + * A constant which indicates the maximum number of objects + * that can be passed into WaitForMultipleObjects. + */ + public static final int MAXIMUM_WAIT_OBJECTS; + /** + * A constant which indicates the maximum length of a pathname. + */ + public static final int MAX_PATH; + /** + * A constant which expresses the concept of the infinite. + */ + public static final int INFINITE; + + /* wait return values */ + /** + * A constant used returned WaitForMultipleObjects when the function times out. + */ + public static final int WAIT_TIMEOUT; + /** + * A constant used by WaitForMultipleObjects to indicate the object which was + * signaled. + */ + public static final int WAIT_OBJECT_0; + /** + * A constant which indicates that some objects which + * were waiting to be signaled are an abandoned mutex + * objects. + */ + public static final int WAIT_ABANDONED_0; + /** + * A constant returned by WaitForMultipleObjects which indicates + * that the wait failed. + */ + public static final int WAIT_FAILED; + + /* wait notification filter masks */ + /** + * Change filter for monitoring file rename, creation or deletion. + */ + public static final int FILE_NOTIFY_CHANGE_FILE_NAME; + /** + * Change filter for monitoring directory creation or deletion. + */ + public static final int FILE_NOTIFY_CHANGE_DIR_NAME; + /** + * Change filter for monitoring file/directory attribute changes. + */ + public static final int FILE_NOTIFY_CHANGE_ATTRIBUTES; + /** + * Change filter for monitoring file size changes. + */ + public static final int FILE_NOTIFY_CHANGE_SIZE; + /** + * Change filter for monitoring the file write timestamp + */ + public static final int FILE_NOTIFY_CHANGE_LAST_WRITE; + /** + * Change filter for monitoring the security descriptors + * of files. + */ + public static final int FILE_NOTIFY_CHANGE_SECURITY; + + /** + * Flag indicating whether or not the OS supports unicode calls. + */ + public static final boolean UNICODE; + /* + * Make requests to set the constants. + */ + static { + System.loadLibrary("win32refresh"); //$NON-NLS-1$ + UNICODE= IsUnicode(); + INVALID_HANDLE_VALUE= INVALID_HANDLE_VALUE(); + ERROR_SUCCESS= ERROR_SUCCESS(); + ERROR_INVALID_HANDLE= ERROR_INVALID_HANDLE(); + + MAXIMUM_WAIT_OBJECTS= MAXIMUM_WAIT_OBJECTS(); + MAX_PATH= MAX_PATH(); + INFINITE= INFINITE(); + + WAIT_TIMEOUT= WAIT_TIMEOUT(); + WAIT_OBJECT_0= WAIT_OBJECT_0(); + WAIT_ABANDONED_0= WAIT_ABANDONED_0(); + WAIT_FAILED= WAIT_FAILED(); + + FILE_NOTIFY_CHANGE_FILE_NAME= FILE_NOTIFY_CHANGE_FILE_NAME(); + FILE_NOTIFY_CHANGE_DIR_NAME= FILE_NOTIFY_CHANGE_DIR_NAME(); + FILE_NOTIFY_CHANGE_ATTRIBUTES= FILE_NOTIFY_CHANGE_ATTRIBUTES(); + FILE_NOTIFY_CHANGE_SIZE= FILE_NOTIFY_CHANGE_SIZE(); + FILE_NOTIFY_CHANGE_LAST_WRITE= FILE_NOTIFY_CHANGE_LAST_WRITE(); + FILE_NOTIFY_CHANGE_SECURITY= FILE_NOTIFY_CHANGE_SECURITY(); + FILE_NOTIFY_ALL= + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_ATTRIBUTES | + FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE | + FILE_NOTIFY_CHANGE_SECURITY; + } + + /** + * Creates a change notification object for the given path. The notification + * object allows the client to monitor changes to the directory and the + * subtree under the directory using FindNextChangeNotification or + * WaitForMultipleObjects. + *

                  + * If the OS supports unicode the path must be no longer than 2^15 - 1 characters. + * Otherwise, the path cannot be longer than MAX_PATH. In either case, if the given + * path is too long ERROR_INVALID_HANDLE is returned. + * + * @param lpPathName The path of the file. + * @param bWatchSubtree If true, specifies that the entire + * tree under the given path should be monitored. If false + * specifies that just the named path should be monitored. + * @param dwNotifyFilter Any combination of FILE_NOTIFY_CHANGE_FILE_NAME, + * FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_ATTRIBUTES, + * FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_CHANGE_LAST_WRITE, or + * FILE_NOTIFY_CHANGE_SECURITY. + * @return long The handle to the find change notification object or + * ERROR_INVALID_HANDLE if the attempt fails. + */ + public static long FindFirstChangeNotification(String lpPathName, boolean bWatchSubtree, int dwNotifyFilter) { + if (UNICODE) + return FindFirstChangeNotificationW(lpPathName, bWatchSubtree, dwNotifyFilter); + return FindFirstChangeNotificationA(Convert.toPlatformBytes(lpPathName), bWatchSubtree, dwNotifyFilter); + } + /** + * Creates a change notification object for the given path. This notification object + * allows the client to monitor changes to the directory and the subtree + * under the directory using FindNextChangeNotification or + * WaitForMultipleObjects. + * + * @param lpPathName The path to the directory to be monitored. Cannot be null, + * or longer than 2^15 - 1 characters. + * @param bWatchSubtree If true, specifies that the entire + * tree under the given path should be monitored. If false + * specifies that just the named path should be monitored. + * @param dwNotifyFilter Any combination of FILE_NOTIFY_CHANGE_FILE_NAME, + * FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_ATTRIBUTES, + * FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_CHANGE_LAST_WRITE, or + * FILE_NOTIFY_CHANGE_SECURITY. + * @return long The handle to the find change notification object or + * ERROR_INVALID_HANDLE if the attempt fails. + */ + private static native long FindFirstChangeNotificationW(String lpPathName, boolean bWatchSubtree, int dwNotifyFilter); + + /** + * Creates a change notification object for the given path. This notification object + * allows the client to monitor changes to the directory and the subtree + * under the directory using FindNextChangeNotification or + * WaitForMultipleObjects. + * + * @param lpPathName The path to the directory to be monitored, cannot be null, + * or be longer + * than MAX_PATH. This path must be in platform bytes converted. + * @param bWatchSubtree If true, specifies that the entire + * tree under the given path should be monitored. If false + * specifies that just the named path should be monitored. + * @param dwNotifyFilter Any combination of FILE_NOTIFY_CHANGE_FILE_NAME, + * FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_ATTRIBUTES, + * FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_CHANGE_LAST_WRITE, or + * FILE_NOTIFY_CHANGE_SECURITY. + * @return long The handle to the find change notification object or + * ERROR_INVALID_HANDLE if the attempt fails. + */ + private static native long FindFirstChangeNotificationA(byte[] lpPathName, boolean bWatchSubtree, int dwNotifyFilter); + + + /** + * Stops and disposes of the change notification object that corresponds to the given + * handle. The handle cannot be used in future calls to FindNextChangeNotification or + * WaitForMultipleObjects + * + * @param hChangeHandle a handle which was created with FindFirstChangeNotification + * @return boolean true if the method succeeds, false + * otherwise. + */ + public static native boolean FindCloseChangeNotification(long hChangeHandle); + + /** + * Requests that the next change detected be signaled. This method should only be + * called after FindFirstChangeNotification or WaitForMultipleObjects. Once this + * method has been called on a given handle, further notification requests can be made + * through the WaitForMultipleObjects call. + * @param hChangeHandle a handle which was created with FindFirstChangeNotification + * @return boolean true if the method succeeds, false otherwise. + */ + public static native boolean FindNextChangeNotification(long hChangeHandle); + + /** + * Returns when one of the following occurs. + *

                    + *
                  • One of the objects is signaled, when bWaitAll is false
                  • + *
                  • All of the objects are signaled, when bWaitAll is true
                  • + *
                  • The timeout interval of dwMilliseconds elapses.
                  • + *
                  + * @param nCount The number of handles, cannot be greater than MAXIMUM_WAIT_OBJECTS. + * @param lpHandles The array of handles to objects to be waited upon cannot contain + * duplicate handles. + * @param bWaitAll If true requires all objects to be signaled before this + * method returns. If false, indicates that only one object need be + * signaled for this method to return. + * @param dwMilliseconds A timeout value in milliseconds. If zero, the function tests + * the objects and returns immediately. If INFINITE, the function will only return + * when the objects have been signaled. + * @return int WAIT_TIMEOUT when the function times out before recieving a signal. + * WAIT_OBJECT_0 + n when a signal for the handle at index n. WAIT_FAILED when this + * function fails. + */ + public static native int WaitForMultipleObjects(int nCount, long[] lpHandles, boolean bWaitAll, int dwMilliseconds); + + /** + * Answers true if the operating system supports + * long filenames. + * @return boolean true if the operating system supports + * long filenames, false otherwise. + */ + private static native boolean IsUnicode(); + + /** + * Answers the last error set in the current thread. + * @return int the last error + */ + public static native int GetLastError(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_LAST_WRITE. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_LAST_WRITE(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_DIR_NAME. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_DIR_NAME(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_ATTRIBUTES. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_ATTRIBUTES(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_SIZE. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_SIZE(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_FILE_NAME. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_FILE_NAME(); + + /** + * Returns the constant FILE_NOTIFY_CHANGE_SECURITY. + * @return int + */ + private static native int FILE_NOTIFY_CHANGE_SECURITY(); + + /** + * Returns the constant MAXIMUM_WAIT_OBJECTS. + * @return int + */ + private static native int MAXIMUM_WAIT_OBJECTS(); + + /** + * Returns the constant MAX_PATH. + * @return int + */ + private static native int MAX_PATH(); + + /** + * Returns the constant INFINITE. + * @return int + */ + private static native int INFINITE(); + + /** + * Returns the constant WAIT_OBJECT_0. + * @return int + */ + private static native int WAIT_OBJECT_0(); + + /** + * Returns the constant WAIT_ABANDONED_0. + * @return int + */ + private static native int WAIT_ABANDONED_0(); + + /** + * Returns the constant WAIT_FAILED. + * @return int + */ + private static native int WAIT_FAILED(); + + /** + * Returns the constant WAIT_TIMEOUT. + * @return int + */ + private static native int WAIT_TIMEOUT(); + + /** + * Returns the constant ERROR_INVALID_HANDLE. + * @return int + */ + private static native int ERROR_INVALID_HANDLE(); + + /** + * Returns the constant ERROR_SUCCESS. + * @return int + */ + private static native int ERROR_SUCCESS(); + + /** + * Returns the constant INVALID_HANDLE_VALUE. + * @return long + */ + private static native long INVALID_HANDLE_VALUE(); + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java new file mode 100644 index 0000000000..fde930f37a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.internal.resources.refresh.win32; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.refresh.*; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * The Win32RefreshProvider creates monitors that + * can monitor drives on Win32 platforms. + * + * @see RefreshProvider + */ +public class Win32RefreshProvider extends RefreshProvider { + private Win32Monitor monitor; + + /** + * Creates a standard Win32 monitor if the given resource is local. + * + * @see RefreshProvider#installMonitor(IResource,IRefreshResult, IProgressMonitor) + */ + @Override + public IRefreshMonitor installMonitor(IResource resource, IRefreshResult result, IProgressMonitor progressMonitor) { + if (resource.getLocation() == null || !resource.exists() || resource.getType() == IResource.FILE) + return null; + if (monitor == null) + monitor = new Win32Monitor(result); + if (monitor.monitor(resource)) + return monitor; + return null; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java new file mode 100644 index 0000000000..5357447992 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.utils; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * An object that iterates over the elements of an array + */ +public class ArrayIterator implements Iterator { + T[] elements; + int index; + int lastElement; + + /** + * Returns new array enumeration over the given object array + */ + public ArrayIterator(T[] elements) { + this(elements, 0, elements.length - 1); + } + + /** + * Returns new array enumeration over the given object array + */ + public ArrayIterator(T[] elements, int firstElement, int lastElement) { + super(); + this.elements = elements; + index = firstElement; + this.lastElement = lastElement; + } + + /** + * Returns true if this enumeration contains more elements. + */ + @Override + public boolean hasNext() { + return elements != null && index <= lastElement; + } + + /** + * Returns the next element of this enumeration. + * @exception NoSuchElementException if no more elements exist. + */ + @Override + public T next() throws NoSuchElementException { + if (!hasNext()) + throw new NoSuchElementException(); + return elements[index++]; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java new file mode 100644 index 0000000000..f3c03aef24 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 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.utils; + +/** + * Utility methods for manipulating bit masks + */ +public class BitMask { + /** + * Returns true if all of the bits indicated by the mask are set. + */ + public static boolean isSet(int flags, int mask) { + return (flags & mask) == mask; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java new file mode 100644 index 0000000000..be02eafc8a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java @@ -0,0 +1,207 @@ +/******************************************************************************* + * 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.utils; + +import org.eclipse.core.runtime.Assert; + +/** + * A cache that keeps a collection of at most maximumCapacity+threshold entries. + * When the number of entries exceeds that limit, least recently used entries are removed + * so the current size is the same as the maximum capacity. + */ +public class Cache { + public class Entry implements KeyedHashSet.KeyedElement { + Object cached; + Object key; + Entry next; + Entry previous; + long timestamp; + + public Entry(Object key, Object cached, long timestamp) { + this.key = key; + this.cached = cached; + this.timestamp = timestamp; + } + + @Override + public boolean compare(KeyedHashSet.KeyedElement other) { + if (!(other instanceof Entry)) + return false; + Entry otherEntry = (Entry) other; + return key.equals(otherEntry.key); + } + + /* Removes this entry from the cache */ + public void discard() { + unchain(); + cached = null; + entries.remove(this); + } + + public Object getCached() { + return cached; + } + + @Override + public Object getKey() { + return key; + } + + @Override + public int getKeyHashCode() { + return key.hashCode(); + } + + public Entry getNext() { + return next; + } + + public Entry getPrevious() { + return previous; + } + + public long getTimestamp() { + return timestamp; + } + + public boolean isHead() { + return previous == null; + } + + public boolean isTail() { + return next == null; + } + + /* Inserts into the head of the list */ + void makeHead() { + Entry oldHead = head; + head = this; + next = oldHead; + previous = null; + if (oldHead == null) + tail = this; + else + oldHead.previous = this; + } + + public void setCached(Object cached) { + this.cached = cached; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public String toString() { + return key + " -> " + cached + " [" + timestamp + ']'; //$NON-NLS-1$ //$NON-NLS-2$ + } + + /* Removes from the linked list, but not from the entries collection */ + void unchain() { + // it may be in the tail + if (tail == this) + tail = previous; + else + next.previous = previous; + // it may be in the head + if (head == this) + head = next; + else + previous.next = next; + } + } + + KeyedHashSet entries; + Entry head; + private int maximumCapacity; + Entry tail; + private double threshold; + + public Cache(int maximumCapacity) { + this(Math.min(KeyedHashSet.MINIMUM_SIZE, maximumCapacity), maximumCapacity, 0.25); + } + + public Cache(int initialCapacity, int maximumCapacity, double threshold) { + Assert.isTrue(maximumCapacity >= initialCapacity, "maximum capacity < initial capacity"); //$NON-NLS-1$ + Assert.isTrue(threshold >= 0 && threshold <= 1, "threshold should be between 0 and 1"); //$NON-NLS-1$ + Assert.isTrue(initialCapacity > 0, "initial capacity must be greater than zero"); //$NON-NLS-1$ + entries = new KeyedHashSet(initialCapacity); + this.maximumCapacity = maximumCapacity; + this.threshold = threshold; + } + + public void addEntry(Object key, Object toCache) { + addEntry(key, toCache, 0); + } + + public Entry addEntry(Object key, Object toCache, long timestamp) { + Entry newHead = (Entry) entries.getByKey(key); + if (newHead == null) + entries.add(newHead = new Entry(key, toCache, timestamp)); + newHead.cached = toCache; + newHead.timestamp = timestamp; + newHead.makeHead(); + int extraEntries = entries.size() - maximumCapacity; + if (extraEntries > maximumCapacity * threshold) + // we have reached our limit - ensure we are under the maximum capacity + // by discarding older entries + packEntries(extraEntries); + return newHead; + } + + public Entry getEntry(Object key) { + return getEntry(key, true); + } + + public Entry getEntry(Object key, boolean update) { + Entry existing = (Entry) entries.getByKey(key); + if (existing == null) + return null; + if (!update) + return existing; + existing.unchain(); + existing.makeHead(); + return existing; + } + + public Entry getHead() { + return head; + } + + public Entry getTail() { + return tail; + } + + private void packEntries(int extraEntries) { + // should remove in an ad-hoc way to get better performance + Entry current = tail; + for (; current != null && extraEntries > 0; extraEntries--) { + current.discard(); + current = current.previous; + } + } + + public long size() { + return entries.size(); + } + + public void discardAll() { + entries.clear(); + head = tail = null; + } + + public void dispose() { + discardAll(); + entries = null; + head = tail = null; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java new file mode 100644 index 0000000000..930b0167f3 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2000, 2017 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.utils; + +import java.nio.charset.StandardCharsets; + +public class Convert { + + /** + * Converts the string argument to a byte array. + */ + public static String fromUTF8(byte[] b) { + return new String(b, StandardCharsets.UTF_8); + } + + /** + * Converts the string argument to a byte array. + */ + public static byte[] toUTF8(String s) { + return s.getBytes(StandardCharsets.UTF_8); + } + + /** + * Performs conversion of a long value to a byte array representation. + * + * @see #bytesToLong(byte[]) + */ + public static byte[] longToBytes(long value) { + + // A long value is 8 bytes in length. + byte[] bytes = new byte[8]; + + // Convert and copy value to byte array: + // -- Cast long to a byte to retrieve least significant byte; + // -- Left shift long value by 8 bits to isolate next byte to be converted; + // -- Repeat until all 8 bytes are converted (long = 64 bits). + // Note: In the byte array, the least significant byte of the long is held in + // the highest indexed array bucket. + + for (int i = 0; i < bytes.length; i++) { + bytes[(bytes.length - 1) - i] = (byte) value; + value >>>= 8; + } + + return bytes; + } + + /** + * Performs conversion of a byte array to a long representation. + * + * @see #longToBytes(long) + */ + public static long bytesToLong(byte[] value) { + + long longValue = 0L; + + // See method convertLongToBytes(long) for algorithm details. + for (int i = 0; i < value.length; i++) { + // Left shift has no effect thru first iteration of loop. + longValue <<= 8; + longValue ^= value[i] & 0xFF; + } + + return longValue; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java new file mode 100644 index 0000000000..450bec6f33 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java @@ -0,0 +1,436 @@ +/******************************************************************************* + * 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 + * Martin Oberhuber (Wind River) - [44107] Add symbolic links to ResourceAttributes API + * James Blackburn (Broadcom Corp.) - ongoing development + * Sergey Prigogin (Google) - ongoing development + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import java.io.*; +import java.net.URI; +import org.eclipse.core.filesystem.*; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.resources.ResourceException; +import org.eclipse.core.internal.resources.Workspace; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.preferences.DefaultScope; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.osgi.service.environment.Constants; +import org.eclipse.osgi.util.NLS; +import org.osgi.service.prefs.BackingStoreException; +import org.osgi.service.prefs.Preferences; + +/** + * Static utility methods for manipulating Files and URIs. + */ +public class FileUtil { + static final boolean MACOSX = Constants.OS_MACOSX.equals(getOS()); + + /** + * Converts a ResourceAttributes object into an IFileInfo object. + * @param attributes The resource attributes + * @return The file info + */ + public static IFileInfo attributesToFileInfo(ResourceAttributes attributes) { + IFileInfo fileInfo = EFS.createFileInfo(); + fileInfo.setAttribute(EFS.ATTRIBUTE_READ_ONLY, attributes.isReadOnly()); + fileInfo.setAttribute(EFS.ATTRIBUTE_EXECUTABLE, attributes.isExecutable()); + fileInfo.setAttribute(EFS.ATTRIBUTE_ARCHIVE, attributes.isArchive()); + fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, attributes.isHidden()); + fileInfo.setAttribute(EFS.ATTRIBUTE_SYMLINK, attributes.isSymbolicLink()); + fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_READ, attributes.isSet(EFS.ATTRIBUTE_GROUP_READ)); + fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_WRITE, attributes.isSet(EFS.ATTRIBUTE_GROUP_WRITE)); + fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE, attributes.isSet(EFS.ATTRIBUTE_GROUP_EXECUTE)); + fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_READ, attributes.isSet(EFS.ATTRIBUTE_OTHER_READ)); + fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_WRITE, attributes.isSet(EFS.ATTRIBUTE_OTHER_WRITE)); + fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE, attributes.isSet(EFS.ATTRIBUTE_OTHER_EXECUTE)); + return fileInfo; + } + + /** + * Converts an IPath into its canonical form for the local file system. + */ + public static IPath canonicalPath(IPath path) { + if (path == null) + return null; + try { + final String pathString = path.toOSString(); + final String canonicalPath = new java.io.File(pathString).getCanonicalPath(); + //only create a new path if necessary + if (canonicalPath.equals(pathString)) + return path; + return new Path(canonicalPath); + } catch (IOException e) { + return path; + } + } + + /** + * For a path on a case-insensitive file system returns the path with the actual + * case as it exists in the file system. If only a prefix of the path exists on + * the file system, the case of remaining part of the returned path is the same + * as in the original path. For a case-sensitive file system returns the original + * path. + *

                  + * This method is similar to java.nio.file.Path.toRealPath(LinkOption.NOFOLLOW_LINKS) + * in Java 1.7. + */ + public static IPath realPath(IPath path) { + if (path == null) + return null; + IFileSystem fileSystem = EFS.getLocalFileSystem(); + if (fileSystem.isCaseSensitive()) + return path; + IPath realPath = path.isAbsolute() ? Path.ROOT : Path.EMPTY; + String device = path.getDevice(); + if (device != null) { + realPath = realPath.setDevice(device.toUpperCase()); + } + IFileStore fileStore = null; + for (int i = 0; i < path.segmentCount(); i++) { + final String segment = path.segment(i); + if (i == 0 && path.isUNC()) { + realPath = realPath.append(segment.toUpperCase()); + realPath = realPath.makeUNC(true); + } else { + if (MACOSX) { + // IFileInfo.getName() may not return the real name of the file on Mac OS X. + // Obtain the real name of the file from a listing of its parent directory. + String[] names = realPath.toFile().list(new FilenameFilter() { + @Override + public boolean accept(File dir, String n) { + return n.equalsIgnoreCase(segment); + } + }); + String realName; + if (names == null || names.length == 0) { + // The remainder of the path doesn't exist on the file system - copy from + // the original path. + realPath = realPath.append(path.removeFirstSegments(realPath.segmentCount())); + break; + } else if (names.length == 1) { + realName = names[0]; + } else { + // More than one file matches the file name. Maybe the file system was + // misreported to be case insensitive. Preserve the original name. + realName = segment; + } + realPath = realPath.append(realName); + } else { + if (fileStore == null) + fileStore = fileSystem.getStore(realPath); + fileStore = fileStore.getChild(segment); + IFileInfo info = fileStore.fetchInfo(); + if (!info.exists()) { + // The remainder of the path doesn't exist on the file system - copy from + // the original path. + realPath = realPath.append(path.removeFirstSegments(realPath.segmentCount())); + break; + } + realPath = realPath.append(info.getName()); + } + } + } + if (path.hasTrailingSeparator()) { + realPath = realPath.addTrailingSeparator(); + } + // Return the original path if it's the same as the real one. + return realPath.equals(path) ? path : realPath; + } + + /** + * Returns the current OS. Equivalent to Platform.getOS(), but tolerant of the platform runtime + * not being present. + */ + private static String getOS() { + return System.getProperty("osgi.os", ""); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Converts a URI into its canonical form. + */ + public static URI canonicalURI(URI uri) { + if (uri == null) + return null; + if (EFS.SCHEME_FILE.equals(uri.getScheme())) { + //only create a new URI if it is different + final IPath inputPath = URIUtil.toPath(uri); + final IPath canonicalPath = canonicalPath(inputPath); + if (inputPath == canonicalPath) + return uri; + return URIUtil.toURI(canonicalPath); + } + return uri; + } + + /** + * Converts a URI by replacing the file system path in the URI with the path + * with the actual case as it exists in the file system. + * + * @see #realPath(IPath) + */ + public static URI realURI(URI uri) { + if (uri == null) + return null; + if (EFS.SCHEME_FILE.equals(uri.getScheme())) { + // Only create a new URI if it is different. + final IPath inputPath = URIUtil.toPath(uri); + final IPath realPath = realPath(inputPath); + if (inputPath == realPath) + return uri; + return URIUtil.toURI(realPath); + } + return uri; + } + + /** + * Returns true if the given file system locations overlap. If "bothDirections" is true, + * this means they are the same, or one is a proper prefix of the other. If "bothDirections" + * is false, this method only returns true if the locations are the same, or the first location + * is a prefix of the second. Returns false if the locations do not overlap + * Does the right thing with respect to case insensitive platforms. + */ + private static boolean computeOverlap(IPath location1, IPath location2, boolean bothDirections) { + IPath one = location1; + IPath two = location2; + // If we are on a case-insensitive file system then convert to all lower case. + if (!Workspace.caseSensitive) { + one = new Path(location1.toOSString().toLowerCase()); + two = new Path(location2.toOSString().toLowerCase()); + } + return one.isPrefixOf(two) || (bothDirections && two.isPrefixOf(one)); + } + + /** + * Returns true if the given file system locations overlap. If "bothDirections" is true, + * this means they are the same, or one is a proper prefix of the other. If "bothDirections" + * is false, this method only returns true if the locations are the same, or the first location + * is a prefix of the second. Returns false if the locations do not overlap + */ + private static boolean computeOverlap(URI location1, URI location2, boolean bothDirections) { + if (location1.equals(location2)) + return true; + String scheme1 = location1.getScheme(); + String scheme2 = location2.getScheme(); + if (scheme1 == null ? scheme2 != null : !scheme1.equals(scheme2)) + return false; + if (EFS.SCHEME_FILE.equals(scheme1) && EFS.SCHEME_FILE.equals(scheme2)) + return computeOverlap(URIUtil.toPath(location1), URIUtil.toPath(location2), bothDirections); + IFileSystem system = null; + try { + system = EFS.getFileSystem(scheme1); + } catch (CoreException e) { + //handled below + } + if (system == null) { + //we are stuck with string comparison + String string1 = location1.toString(); + String string2 = location2.toString(); + return string1.startsWith(string2) || (bothDirections && string2.startsWith(string1)); + } + IFileStore store1 = system.getStore(location1); + IFileStore store2 = system.getStore(location2); + return store1.equals(store2) || store1.isParentOf(store2) || (bothDirections && store2.isParentOf(store1)); + } + + /** + * Converts an IFileInfo object into a ResourceAttributes object. + * @param fileInfo The file info + * @return The resource attributes + */ + public static ResourceAttributes fileInfoToAttributes(IFileInfo fileInfo) { + ResourceAttributes attributes = new ResourceAttributes(); + attributes.setReadOnly(fileInfo.getAttribute(EFS.ATTRIBUTE_READ_ONLY)); + attributes.setArchive(fileInfo.getAttribute(EFS.ATTRIBUTE_ARCHIVE)); + attributes.setExecutable(fileInfo.getAttribute(EFS.ATTRIBUTE_EXECUTABLE)); + attributes.setHidden(fileInfo.getAttribute(EFS.ATTRIBUTE_HIDDEN)); + attributes.setSymbolicLink(fileInfo.getAttribute(EFS.ATTRIBUTE_SYMLINK)); + attributes.set(EFS.ATTRIBUTE_GROUP_READ, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_READ)); + attributes.set(EFS.ATTRIBUTE_GROUP_WRITE, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_WRITE)); + attributes.set(EFS.ATTRIBUTE_GROUP_EXECUTE, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE)); + attributes.set(EFS.ATTRIBUTE_OTHER_READ, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_READ)); + attributes.set(EFS.ATTRIBUTE_OTHER_WRITE, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_WRITE)); + attributes.set(EFS.ATTRIBUTE_OTHER_EXECUTE, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE)); + return attributes; + } + + private static String getLineSeparatorFromPreferences(Preferences node) { + try { + // be careful looking up for our node so not to create any nodes as side effect + if (node.nodeExists(Platform.PI_RUNTIME)) + return node.node(Platform.PI_RUNTIME).get(Platform.PREF_LINE_SEPARATOR, null); + } catch (BackingStoreException e) { + // ignore + } + return null; + } + + /** + * Returns line separator appropriate for the given file. The returned value + * will be the first available value from the list below: + *

                    + *
                  1. Line separator currently used in that file. + *
                  2. Line separator defined in project preferences. + *
                  3. Line separator defined in instance preferences. + *
                  4. Line separator defined in default preferences. + *
                  5. Operating system default line separator. + *
                  + * @param file the file for which line separator should be returned + * @return line separator for the given file + */ + public static String getLineSeparator(IFile file) { + if (file.exists()) { + InputStream input = null; + try { + input = file.getContents(); + int c = input.read(); + while (c != -1 && c != '\r' && c != '\n') + c = input.read(); + if (c == '\n') + return "\n"; //$NON-NLS-1$ + if (c == '\r') { + if (input.read() == '\n') + return "\r\n"; //$NON-NLS-1$ + return "\r"; //$NON-NLS-1$ + } + } catch (CoreException e) { + // ignore + } catch (IOException e) { + // ignore + } finally { + safeClose(input); + } + } + Preferences rootNode = Platform.getPreferencesService().getRootNode(); + String value = null; + // if the file does not exist or has no content yet, try with project preferences + value = getLineSeparatorFromPreferences(rootNode.node(ProjectScope.SCOPE).node(file.getProject().getName())); + if (value != null) + return value; + // try with instance preferences + value = getLineSeparatorFromPreferences(rootNode.node(InstanceScope.SCOPE)); + if (value != null) + return value; + // try with default preferences + value = getLineSeparatorFromPreferences(rootNode.node(DefaultScope.SCOPE)); + if (value != null) + return value; + // if there is no preference set, fall back to OS default value + return System.getProperty("line.separator"); //$NON-NLS-1$ + } + + /** + * Returns true if the given file system locations overlap, and false otherwise. + * Overlap means the locations are the same, or one is a proper prefix of the other. + */ + public static boolean isOverlapping(URI location1, URI location2) { + return computeOverlap(location1, location2, true); + } + + /** + * Returns true if location1 is the same as, or a proper prefix of, location2. + * Returns false otherwise. + */ + public static boolean isPrefixOf(IPath location1, IPath location2) { + return computeOverlap(location1, location2, false); + } + + /** + * Returns true if location1 is the same as, or a proper prefix of, location2. + * Returns false otherwise. + */ + public static boolean isPrefixOf(URI location1, URI location2) { + return computeOverlap(location1, location2, false); + } + + /** + * Closes a stream and ignores any resulting exception. This is useful + * when doing stream cleanup in a finally block where secondary exceptions + * are not worth logging. + * + *

                  + * WARNING: + * If the API contract requires notifying clients of I/O problems, then you must + * explicitly close() output streams outside of safeClose(). + * Some OutputStreams will defer an IOException from write() to close(). So + * while the writes may 'succeed', ignoring the IOExcpetion will result in silent + * data loss. + *

                  + *

                  + * This method should only be used as a fail-safe to ensure resources are not + * leaked. + *

                  + * See also: https://bugs.eclipse.org/bugs/show_bug.cgi?id=332543 + */ + public static void safeClose(Closeable stream) { + try { + if (stream != null) + stream.close(); + } catch (IOException e) { + //ignore + } + } + + /** + * Converts a URI to an IPath. Returns null if the URI cannot be represented + * as an IPath. + *

                  + * Note this method differs from URIUtil in its handling of relative URIs + * as being relative to path variables. + */ + public static IPath toPath(URI uri) { + if (uri == null) + return null; + final String scheme = uri.getScheme(); + // null scheme represents path variable + if (scheme == null || EFS.SCHEME_FILE.equals(scheme)) + return new Path(uri.getSchemeSpecificPart()); + return null; + } + + public static final void transferStreams(InputStream source, OutputStream destination, String path, IProgressMonitor monitor) throws CoreException { + SubMonitor subMonitor = SubMonitor.convert(monitor); + try { + byte[] buffer = new byte[8192]; + while (true) { + int bytesRead = -1; + try { + bytesRead = source.read(buffer); + } catch (IOException e) { + String msg = NLS.bind(Messages.localstore_failedReadDuringWrite, path); + throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, new Path(path), msg, e); + } + try { + if (bytesRead == -1) { + // Bug 332543 - ensure we don't ignore failures on close() + destination.close(); + break; + } + destination.write(buffer, 0, bytesRead); + } catch (IOException e) { + String msg = NLS.bind(Messages.localstore_couldNotWrite, path); + throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, new Path(path), msg, e); + } + subMonitor.split(1); + } + } finally { + safeClose(source); + safeClose(destination); + } + } + + /** + * Not intended for instantiation. + */ + private FileUtil() { + super(); + } +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.java new file mode 100644 index 0000000000..762175c72c --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.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.utils; + +/** + * A string pool participant is used for sharing strings between several + * unrelated parties. Typically a single StringPool instance + * will be created, and a group of participants will be asked to store their + * strings in the pool. This allows participants to share equal strings + * without creating explicit dependencies between each other. + *

                  + * Clients may implement this interface. + *

                  + * + * @see StringPool + * @since 3.1 + */ +public interface IStringPoolParticipant { + /** + * Instructs this participant to share its strings in the provided + * pool. + */ + public void shareStrings(StringPool pool); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java new file mode 100644 index 0000000000..4346f348c5 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java @@ -0,0 +1,243 @@ +/******************************************************************************* + * 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.utils; + + +/** Adapted from a homonym class in org.eclipse.osgi. A hash table of + * KeyedElements. + */ + +public class KeyedHashSet { + public interface KeyedElement { + + public boolean compare(KeyedElement other); + + public Object getKey(); + + public int getKeyHashCode(); + } + + protected static final int MINIMUM_SIZE = 7; + private int capacity; + protected int elementCount = 0; + protected KeyedElement[] elements; + protected boolean replace; + + public KeyedHashSet(int capacity) { + this(capacity, true); + } + + public KeyedHashSet(int capacity, boolean replace) { + elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)]; + this.replace = replace; + this.capacity = capacity; + } + + /** + * Adds an element to this set. If an element with the same key already exists, + * replaces it depending on the replace flag. + * @return true if the element was added/stored, false otherwise + */ + public boolean add(KeyedElement element) { + int hash = hash(element); + + // 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 true; + } + if (elements[i].compare(element)) { + if (replace) + elements[i] = element; + return replace; + } + } + + // 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 true; + } + if (elements[i].compare(element)) { + if (replace) + elements[i] = element; + return replace; + } + } + + // if we didn't find a free slot, then try again with the expanded set + expand(); + return add(element); + } + + public void clear() { + elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)]; + elementCount = 0; + } + + /** + * The array isn't large enough so double its size and rehash + * all its current values. + */ + protected void expand() { + KeyedElement[] oldElements = elements; + elements = new KeyedElement[elements.length * 2]; + + int maxArrayIndex = elements.length - 1; + for (int i = 0; i < oldElements.length; i++) { + KeyedElement element = oldElements[i]; + if (element != null) { + int hash = hash(element); + while (elements[hash] != null) { + hash++; + if (hash > maxArrayIndex) + hash = 0; + } + elements[hash] = element; + } + } + } + + /** + * Returns the set element with the given id, or null + * if not found. + */ + public KeyedElement getByKey(Object key) { + if (elementCount == 0) + return null; + int hash = keyHash(key); + + // search the last half of the array + for (int i = hash; i < elements.length; i++) { + KeyedElement element = elements[i]; + if (element == null) + return null; + if (element.getKey().equals(key)) + return element; + } + + // search the beginning of the array + for (int i = 0; i < hash - 1; i++) { + KeyedElement element = elements[i]; + if (element == null) + return null; + if (element.getKey().equals(key)) + return element; + } + + // nothing found so return null + return null; + } + + private int hash(KeyedElement key) { + return Math.abs(key.getKeyHashCode()) % elements.length; + } + + private int keyHash(Object key) { + return Math.abs(key.hashCode()) % elements.length; + } + + /** + * 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; + KeyedElement element = elements[index]; + while (element != null) { + int hashIndex = hash(element); + 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 boolean remove(KeyedElement toRemove) { + if (elementCount == 0) + return false; + + int hash = hash(toRemove); + + for (int i = hash; i < elements.length; i++) { + KeyedElement element = elements[i]; + if (element == null) + return false; + if (element.compare(toRemove)) { + rehashTo(i); + elementCount--; + return true; + } + } + + for (int i = 0; i < hash - 1; i++) { + KeyedElement element = elements[i]; + if (element == null) + return false; + if (element.compare(toRemove)) { + rehashTo(i); + elementCount--; + return true; + } + } + return false; + } + + private boolean shouldGrow() { + return elementCount > elements.length * 0.75; + } + + public int size() { + return elementCount; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(100); + result.append('{'); + boolean first = true; + for (int i = 0; i < elements.length; i++) { + if (elements[i] != null) { + if (first) + first = false; + else + result.append(", "); //$NON-NLS-1$ + result.append(elements[i]); + } + } + result.append('}'); + return result.toString(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java new file mode 100644 index 0000000000..34e8a2b4fb --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java @@ -0,0 +1,333 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 - Initial API and implementation + * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + * Francis Lynch (Wind River) - [301563] Save and load tree snapshots + * Martin Oberhuber (Wind River) - [306575] Save snapshot location with project + * Mikael Barbero (Eclipse Foundation) - [286681] handle WAIT_ABANDONED_0 return value + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.core.internal.utils.messages"; //$NON-NLS-1$ + + // dtree + public static String dtree_immutable; + public static String dtree_malformedTree; + public static String dtree_missingChild; + public static String dtree_notFound; + public static String dtree_notImmutable; + public static String dtree_reverse; + public static String dtree_subclassImplement; + public static String dtree_switchError; + + // events + public static String events_builderError; + public static String events_building_0; + public static String events_building_1; + public static String events_errors; + public static String events_instantiate_1; + public static String events_invoking_1; + public static String events_invoking_2; + public static String events_skippingBuilder; + public static String events_unknown; + + public static String history_copyToNull; + public static String history_copyToSelf; + public static String history_errorContentDescription; + public static String history_notValid; + public static String history_problemsCleaning; + + public static String links_creating; + public static String links_errorLinkReconcile; + public static String links_invalidLocation; + public static String links_localDoesNotExist; + public static String links_locationOverlapsLink; + public static String links_locationOverlapsProject; + public static String links_natureVeto; + public static String links_noPath; + public static String links_overlappingResource; + public static String links_parentNotAccessible; + public static String links_notFileFolder; + public static String links_updatingDuplicate; + public static String links_vetoNature; + public static String links_workspaceVeto; + public static String links_wrongLocalType; + public static String links_resourceIsNotALink; + public static String links_setLocation; + + public static String group_invalidParent; + public static String filters_missingFilterType; + + // local store + public static String localstore_copying; + public static String localstore_copyProblem; + public static String localstore_couldnotDelete; + public static String localstore_couldNotMove; + public static String localstore_couldNotRead; + public static String localstore_couldNotWrite; + public static String localstore_couldNotWriteReadOnly; + public static String localstore_deleteProblem; + public static String localstore_deleting; + public static String localstore_failedReadDuringWrite; + public static String localstore_fileExists; + public static String localstore_fileNotFound; + public static String localstore_locationUndefined; + public static String localstore_refreshing; + public static String localstore_refreshingRoot; + public static String localstore_resourceExists; + public static String localstore_resourceDoesNotExist; + public static String localstore_resourceIsOutOfSync; + + // resource mappings and models + public static String mapping_invalidDef; + public static String mapping_wrongType; + public static String mapping_noIdentifier; + public static String mapping_validate; + public static String mapping_multiProblems; + + // internal.resources + public static String natures_duplicateNature; + public static String natures_hasCycle; + public static String natures_invalidDefinition; + public static String natures_invalidRemoval; + public static String natures_invalidSet; + public static String natures_missingIdentifier; + public static String natures_missingNature; + public static String natures_missingPrerequisite; + public static String natures_multipleSetMembers; + + public static String pathvar_beginLetter; + public static String pathvar_invalidChar; + public static String pathvar_invalidValue; + public static String pathvar_length; + public static String pathvar_undefined; + public static String pathvar_whitespace; + + public static String preferences_deleteException; + public static String preferences_loadException; + public static String preferences_operationCanceled; + public static String preferences_removeNodeException; + public static String preferences_clearNodeException; + public static String preferences_saveProblems; + public static String preferences_syncException; + + public static String projRead_badArguments; + public static String projRead_badFilterName; + public static String projRead_badFilterID; + public static String projRead_badFilterType; + public static String projRead_badFilterType2; + public static String projRead_badID; + public static String projRead_badLinkLocation; + public static String projRead_badLinkName; + public static String projRead_badLinkType; + public static String projRead_badLinkType2; + public static String projRead_badLocation; + public static String projRead_badSnapshotLocation; + public static String projRead_cannotReadSnapshot; + public static String projRead_emptyFilterName; + public static String projRead_emptyLinkName; + public static String projRead_emptyVariableName; + public static String projRead_failureReadingProjectDesc; + public static String projRead_notProjectDescription; + public static String projRead_whichKey; + public static String projRead_whichValue; + public static String projRead_missingProjectName; + + public static String properties_couldNotClose; + public static String properties_qualifierIsNull; + public static String properties_readProperties; + public static String properties_valueTooLong; + + // auto-refresh + public static String refresh_installError; + public static String refresh_installMonitorsOnWorkspace; + public static String refresh_jobName; + public static String refresh_pollJob; + public static String refresh_refreshErr; + public static String refresh_restoreOnInvalid; + public static String refresh_task; + public static String refresh_uninstallMonitorsOnWorkspace; + + public static String resources_cannotModify; + public static String resources_changeInAdd; + public static String resources_charsetBroadcasting; + public static String resources_charsetUpdating; + public static String resources_closing_0; + public static String resources_closing_1; + public static String resources_copyDestNotSub; + public static String resources_copying; + public static String resources_copying_0; + public static String resources_copyNotMet; + public static String resources_copyProblem; + public static String resources_couldnotDelete; + public static String resources_create; + public static String resources_creating; + public static String resources_deleteMeta; + public static String resources_deleteProblem; + public static String resources_deleting; + public static String resources_deleting_0; + public static String resources_destNotNull; + public static String resources_errorContentDescription; + public static String resources_errorDeleting; + public static String resources_errorMarkersDelete; + public static String resources_errorMarkersMove; + public static String resources_wrongMarkerAttributeValueType; + public static String resources_errorMembers; + public static String resources_errorMoving; + public static String resources_errorMultiRefresh; + public static String resources_errorNature; + public static String resources_errorPropertiesMove; + public static String resources_errorReadProject; + public static String resources_errorRefresh; + public static String resources_errorValidator; + public static String resources_errorVisiting; + public static String resources_existsDifferentCase; + public static String resources_existsLocalDifferentCase; + public static String resources_exMasterTable; + public static String resources_exReadProjectLocation; + public static String resources_exSafeRead; + public static String resources_exSafeSave; + public static String resources_exSaveMaster; + public static String resources_exSaveProjectLocation; + public static String resources_fileExists; + public static String resources_fileToProj; + public static String resources_flushingContentDescriptionCache; + public static String resources_folderOverFile; + public static String resources_format; + public static String resources_initHook; + public static String resources_initTeamHook; + public static String resources_initValidator; + public static String resources_invalidCharInName; + public static String resources_invalidCharInPath; + public static String resources_invalidName; + public static String resources_invalidPath; + public static String resources_invalidProjDesc; + public static String resources_invalidResourceName; + public static String resources_invalidRoot; + public static String resources_markerNotFound; + public static String resources_missingProjectMeta; + public static String resources_missingProjectMetaRepaired; + public static String resources_moveDestNotSub; + public static String resources_moveMeta; + public static String resources_moveNotMet; + public static String resources_moveNotProject; + public static String resources_moveProblem; + public static String resources_moveRoot; + public static String resources_moving; + public static String resources_moving_0; + public static String resources_mustBeAbsolute; + public static String resources_mustBeLocal; + public static String resources_mustBeOpen; + public static String resources_mustExist; + public static String resources_mustNotExist; + public static String resources_nameEmpty; + public static String resources_nameNull; + public static String resources_natureClass; + public static String resources_natureDeconfig; + public static String resources_natureExtension; + public static String resources_natureFormat; + public static String resources_natureImplement; + public static String resources_notChild; + public static String resources_oneHook; + public static String resources_oneTeamHook; + public static String resources_oneValidator; + public static String resources_opening_1; + public static String resources_overlapWorkspace; + public static String resources_overlapProject; + public static String resources_pathNull; + public static String resources_projectDesc; + public static String resources_projectDescSync; + public static String resources_projectMustNotBeOpen; + public static String resources_projectPath; + public static String resources_pruningHistory; + public static String resources_reading; + public static String resources_readingEncoding; + public static String resources_readingSnap; + public static String resources_readMarkers; + public static String resources_readMeta; + public static String resources_readMetaWrongVersion; + public static String resources_readOnly; + public static String resources_readOnly2; + public static String resources_readProjectMeta; + public static String resources_readProjectTree; + public static String resources_readSync; + public static String resources_readWorkspaceMeta; + public static String resources_readWorkspaceMetaValue; + public static String resources_readWorkspaceSnap; + public static String resources_readWorkspaceTree; + public static String resources_refreshing; + public static String resources_refreshingRoot; + public static String resources_resetMarkers; + public static String resources_resetSync; + public static String resources_resourcePath; + public static String resources_saveOp; + public static String resources_saveProblem; + public static String resources_saveWarnings; + public static String resources_saving_0; + public static String resources_savingEncoding; + public static String resources_setDesc; + public static String resources_setLocal; + public static String resources_settingCharset; + public static String resources_settingContents; + public static String resources_settingDefaultCharsetContainer; + public static String resources_settingDerivedFlag; + public static String resources_shutdown; + public static String resources_shutdownProblems; + public static String resources_snapInit; + public static String resources_snapRead; + public static String resources_snapRequest; + public static String resources_snapshot; + public static String resources_startupProblems; + public static String resources_touch; + public static String resources_updating; + public static String resources_updatingEncoding; + public static String resources_workspaceClosed; + public static String resources_workspaceOpen; + public static String resources_writeMeta; + public static String resources_writeWorkspaceMeta; + public static String resources_errorResourceIsFiltered; + + public static String synchronizer_partnerNotRegistered; + + // URL + public static String url_badVariant; + public static String url_couldNotResolve_projectDoesNotExist; + public static String url_couldNotResolve_URLProtocolHandlerCanNotResolveURL; + public static String url_couldNotResolve_resourceLocationCanNotBeDetermined; + + // utils + public static String utils_clone; + public static String utils_stringJobName; + // watson + public static String watson_elementNotFound; + public static String watson_illegalSubtree; + public static String watson_immutable; + public static String watson_noModify; + public static String watson_nullArg; + public static String watson_unknown; + + // auto-refresh win32 native + public static String WM_beginTask; + public static String WM_errCloseHandle; + public static String WM_errCreateHandle; + public static String WM_errFindChange; + public static String WM_errors; + public static String WM_jobName; + public static String WM_nativeErr; + public static String WM_mutexAbandoned; + + static { + // initialize resource bundles + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java new file mode 100644 index 0000000000..dd5b0cd60f --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java @@ -0,0 +1,330 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import java.util.*; + +/** + * A specialized map implementation that is optimized for a + * small set of object keys. + * + * Implemented as a single array that alternates keys and values. + */ +@SuppressWarnings("unchecked") +public class ObjectMap implements Map, IStringPoolParticipant { + + // 8 attribute keys, 8 attribute values + protected static final int DEFAULT_SIZE = 16; + protected static final int GROW_SIZE = 10; + protected int count = 0; + protected Object[] elements = null; + + /** + * Creates a new object map of default size + */ + public ObjectMap() { + this(DEFAULT_SIZE); + } + + /** + * Creates a new object map. + * @param initialCapacity The initial number of elements that will fit in the map. + */ + public ObjectMap(int initialCapacity) { + if (initialCapacity > 0) + elements = new Object[Math.max(initialCapacity * 2, 0)]; + } + + /** + * Creates a new object map of the same size as the given map and + * populate it with the key/attribute pairs found in the map. + * @param map The entries in the given map will be added to the new map. + */ + public ObjectMap(Map map) { + this(map.size()); + putAll(map); + } + + /** + * @see Map#clear() + */ + @Override + public void clear() { + elements = null; + count = 0; + } + + /** + * @see java.lang.Object#clone() + */ + @Override + public Object clone() { + return new ObjectMap<>(this); + } + + /** + * @see Map#containsKey(java.lang.Object) + */ + @Override + public boolean containsKey(Object key) { + if (elements == null || count == 0) + return false; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return true; + return false; + } + + /** + * @see Map#containsValue(java.lang.Object) + */ + @Override + public boolean containsValue(Object value) { + if (elements == null || 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; + } + + /** + * @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 count == 0 ? Collections.EMPTY_SET : toHashMap().entrySet(); + } + + /** + * See Object#equals + */ + @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; + + //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; + } + + /** + * @see Map#get(java.lang.Object) + */ + @Override + public V get(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return (V) elements[i + 1]; + return null; + } + + /** + * The capacity of the map has been exceeded, grow the array by + * GROW_SIZE to accommodate more entries. + */ + protected void grow() { + Object[] expanded = new Object[elements.length + GROW_SIZE]; + System.arraycopy(elements, 0, expanded, 0, elements.length); + elements = expanded; + } + + /** + * See Object#hashCode + */ + @Override + public int hashCode() { + int hash = 0; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + hash += elements[i].hashCode(); + } + } + return hash; + } + + /** + * @see Map#isEmpty() + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * @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()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.add((K) elements[i]); + } + } + return result; + } + + /** + * @see Map#put(java.lang.Object, java.lang.Object) + */ + @Override + public V put(K key, V value) { + if (key == null) + throw new NullPointerException(); + if (value == null) + return remove(key); + + // handle the case where we don't have any attributes yet + if (elements == null) + elements = new Object[DEFAULT_SIZE]; + if (count == 0) { + elements[0] = key; + elements[1] = value; + count++; + return null; + } + + int emptyIndex = -1; + // replace existing value if it exists + for (int i = 0; i < elements.length; i += 2) { + if (elements[i] != null) { + if (elements[i].equals(key)) { + Object oldValue = elements[i + 1]; + elements[i + 1] = value; + return (V) oldValue; + } + } else if (emptyIndex == -1) { + // keep track of the first empty index + emptyIndex = i; + } + } + // this will put the emptyIndex greater than the size but + // that's ok because we will grow first. + if (emptyIndex == -1) + emptyIndex = count * 2; + + // otherwise add it to the list of elements. + // grow if necessary + if (elements.length <= (count * 2)) + grow(); + elements[emptyIndex] = key; + elements[emptyIndex + 1] = value; + count++; + return null; + } + + /** + * @see Map#putAll(java.util.Map) + */ + @Override + public void putAll(Map map) { + for (Map.Entry e : map.entrySet()) + put(e.getKey(), e.getValue()); + } + + /** + * @see Map#remove(java.lang.Object) + */ + @Override + public V remove(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null && elements[i].equals(key)) { + elements[i] = null; + Object result = elements[i + 1]; + elements[i + 1] = null; + count--; + return (V) result; + } + } + return null; + } + + /** + * @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; + 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); + } + } + + /** + * Creates a new hash map with the same contents as this map. + */ + private HashMap toHashMap() { + HashMap result = new HashMap<>(size()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.put((K) elements[i], (V) elements[i + 1]); + } + } + return result; + } + + /** + * @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()); + 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/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java new file mode 100644 index 0000000000..125011218b --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * 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.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.service.debug.DebugOptions; +import org.eclipse.osgi.service.debug.DebugOptionsListener; +import org.osgi.framework.Bundle; + +public class Policy { + public static final DebugOptionsListener RESOURCES_DEBUG_OPTIONS_LISTENER = new DebugOptionsListener() { + @Override + public void optionsChanged(DebugOptions options) { + DEBUG = options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/debug", false); //$NON-NLS-1$ + + DEBUG_AUTO_REFRESH = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/refresh", false); //$NON-NLS-1$ + + DEBUG_BUILD_DELTA = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/delta", false); //$NON-NLS-1$ + DEBUG_BUILD_FAILURE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/failure", false); //$NON-NLS-1$ + DEBUG_BUILD_INTERRUPT = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/interrupt", false); //$NON-NLS-1$ + DEBUG_BUILD_INVOKING = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/invoking", false); //$NON-NLS-1$ + DEBUG_BUILD_NEEDED = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/needbuild", false); //$NON-NLS-1$ + DEBUG_BUILD_NEEDED_STACK = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/needbuildstack", false); //$NON-NLS-1$ + DEBUG_BUILD_STACK = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/build/stacktrace", false); //$NON-NLS-1$ + + DEBUG_CONTENT_TYPE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/contenttype", false); //$NON-NLS-1$ + DEBUG_CONTENT_TYPE_CACHE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/contenttype/cache", false); //$NON-NLS-1$ + DEBUG_HISTORY = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/history", false); //$NON-NLS-1$ + DEBUG_NATURES = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/natures", false); //$NON-NLS-1$ + DEBUG_NOTIFICATIONS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/notifications", false); //$NON-NLS-1$ + DEBUG_PREFERENCES = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/preferences", false); //$NON-NLS-1$ + + DEBUG_RESTORE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore", false); //$NON-NLS-1$ + DEBUG_RESTORE_MARKERS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/markers", false); //$NON-NLS-1$ + DEBUG_RESTORE_MASTERTABLE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/mastertable", false); //$NON-NLS-1$ + DEBUG_RESTORE_METAINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/metainfo", false); //$NON-NLS-1$ + DEBUG_RESTORE_SNAPSHOTS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/snapshots", false); //$NON-NLS-1$ + DEBUG_RESTORE_SYNCINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/syncinfo", false); //$NON-NLS-1$ + DEBUG_RESTORE_TREE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/restore/tree", false); //$NON-NLS-1$ + + DEBUG_SAVE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save", false); //$NON-NLS-1$ + DEBUG_SAVE_MARKERS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/markers", false); //$NON-NLS-1$ + DEBUG_SAVE_MASTERTABLE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/mastertable", false); //$NON-NLS-1$ + DEBUG_SAVE_METAINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/metainfo", false); //$NON-NLS-1$ + DEBUG_SAVE_SYNCINFO = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/syncinfo", false); //$NON-NLS-1$ + DEBUG_SAVE_TREE = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/save/tree", false); //$NON-NLS-1$ + + DEBUG_STRINGS = DEBUG && options.getBooleanOption(ResourcesPlugin.PI_RESOURCES + "/strings", false); //$NON-NLS-1$ + } + }; + + public static final boolean buildOnCancel = false; + //general debug flag for the plugin + public static boolean DEBUG = false; + + public static boolean DEBUG_AUTO_REFRESH = false; + + //debug constants + public static boolean DEBUG_BUILD_DELTA = false; + public static boolean DEBUG_BUILD_FAILURE = false; + public static boolean DEBUG_BUILD_INTERRUPT = false; + public static boolean DEBUG_BUILD_INVOKING = false; + public static boolean DEBUG_BUILD_NEEDED = false; + public static boolean DEBUG_BUILD_NEEDED_STACK = false; + public static boolean DEBUG_BUILD_STACK = false; + + public static boolean DEBUG_CONTENT_TYPE = false; + public static boolean DEBUG_CONTENT_TYPE_CACHE = false; + public static boolean DEBUG_HISTORY = false; + public static boolean DEBUG_NATURES = false; + public static boolean DEBUG_NOTIFICATIONS = false; + public static boolean DEBUG_PREFERENCES = false; + // Get timing information for restoring data + public static boolean DEBUG_RESTORE = false; + public static boolean DEBUG_RESTORE_MARKERS = false; + public static boolean DEBUG_RESTORE_MASTERTABLE = false; + + public static boolean DEBUG_RESTORE_METAINFO = false; + public static boolean DEBUG_RESTORE_SNAPSHOTS = false; + public static boolean DEBUG_RESTORE_SYNCINFO = false; + public static boolean DEBUG_RESTORE_TREE = false; + // Get timing information for save and snapshot data + public static boolean DEBUG_SAVE = false; + public static boolean DEBUG_SAVE_MARKERS = false; + public static boolean DEBUG_SAVE_MASTERTABLE = false; + + public static boolean DEBUG_SAVE_METAINFO = false; + public static boolean DEBUG_SAVE_SYNCINFO = false; + public static boolean DEBUG_SAVE_TREE = false; + public static boolean DEBUG_STRINGS = false; + public static final long MAX_BUILD_DELAY = 1000; + + public static final long MIN_BUILD_DELAY = 100; + public static int opWork = 100; + public static final int totalWork = 100; + + public static void checkCanceled(IProgressMonitor monitor) { + if (monitor.isCanceled()) + throw new OperationCanceledException(); + } + + /** + * Print a debug message to the console. + * Prepend the message with the current date, the name of the current thread and the current job if present. + */ + public static void debug(String message) { + StringBuilder output = new StringBuilder(); + output.append(new Date(System.currentTimeMillis())); + output.append(" - ["); //$NON-NLS-1$ + output.append(Thread.currentThread().getName()); + output.append("] "); //$NON-NLS-1$ + Job currentJob = Job.getJobManager().currentJob(); + if (currentJob != null) { + output.append(currentJob.getClass().getName()); + output.append("("); //$NON-NLS-1$ + output.append(currentJob.getName()); + output.append("): "); //$NON-NLS-1$ + } + output.append(message); + System.out.println(output.toString()); + } + + /** + * Print a debug throwable to the console. + */ + public static void debug(Throwable t) { + StringWriter writer = new StringWriter(); + t.printStackTrace(new PrintWriter(writer)); + String str = writer.toString(); + if (str.endsWith("\n")) //$NON-NLS-1$ + str = str.substring(0, str.length() - 2); + debug(str); + } + + public static void log(int severity, String message, Throwable t) { + if (message == null) + message = ""; //$NON-NLS-1$ + log(new Status(severity, ResourcesPlugin.PI_RESOURCES, 1, message, t)); + } + + public static void log(IStatus status) { + final Bundle bundle = Platform.getBundle(ResourcesPlugin.PI_RESOURCES); + if (bundle == null) + return; + Platform.getLog(bundle).log(status); + } + + /** + * Logs a throwable, assuming severity of error + * @param t + */ + public static void log(Throwable t) { + log(IStatus.ERROR, "Internal Error", t); //$NON-NLS-1$ + } + + public static IProgressMonitor monitorFor(IProgressMonitor monitor) { + return monitor == null ? new NullProgressMonitor() : monitor; + } + + public static IProgressMonitor subMonitorFor(IProgressMonitor monitor, int ticks) { + if (monitor == null) + return new NullProgressMonitor(); + if (monitor instanceof NullProgressMonitor) + return monitor; + return new SubProgressMonitor(monitor, ticks); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java new file mode 100644 index 0000000000..0c98970ee4 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * 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.utils; + +import java.util.Collections; +import java.util.Iterator; + +/** + * A Queue of objects. + */ +@SuppressWarnings("unchecked") +public class Queue { + protected Object[] elements; + protected int head; + protected int tail; + protected boolean reuse; + + public Queue() { + this(20, false); + } + + /** + * The parameter reuse indicates what do you want to happen with + * the object reference when you remove it from the queue. If + * reuse is false the queue no longer holds a reference to the + * object when it is removed. If reuse is true you can use the + * method getNextAvailableObject to get an used object, set its + * new values and add it again to the queue. + */ + public Queue(int size, boolean reuse) { + elements = new Object[size]; + head = tail = 0; + this.reuse = reuse; + } + + public void add(T element) { + int newTail = increment(tail); + if (newTail == head) { + grow(); + newTail = tail + 1; + } + elements[tail] = element; + tail = newTail; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to decrement an index in the queue. + */ + public int decrement(int index) { + return (index == 0) ? (elements.length - 1) : index - 1; + } + + public T elementAt(int index) { + return (T)elements[index]; + } + + @SuppressWarnings("rawtypes") + public Iterator iterator() { + /**/ + if (isEmpty()) + return Collections.EMPTY_LIST.iterator(); + + /* if head < tail we can use the same array */ + if (head <= tail) + return new ArrayIterator(elements, head, tail - 1); + + /* otherwise we need to create a new array */ + Object[] newElements = new Object[size()]; + int end = (elements.length - head); + System.arraycopy(elements, head, newElements, 0, end); + System.arraycopy(elements, 0, newElements, end, tail); + return new ArrayIterator(newElements); + } + + /** + * Returns an object that has been removed from the queue, if any. + * The intention is to support reuse of objects that have already + * been processed and removed from the queue. Returns null if there + * are no available objects. + */ + public T getNextAvailableObject() { + int index = tail; + while (index != head) { + if (elements[index] != null) { + T result = (T)elements[index]; + elements[index] = null; + return result; + } + index = increment(index); + } + return null; + } + + protected void grow() { + int newSize = (int) (elements.length * 1.5); + Object[] newElements = new Object[newSize]; + if (tail >= head) + System.arraycopy(elements, head, newElements, head, size()); + else { + int newHead = newSize - (elements.length - head); + System.arraycopy(elements, 0, newElements, 0, tail + 1); + System.arraycopy(elements, head, newElements, newHead, (newSize - newHead)); + head = newHead; + } + elements = newElements; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to increment an index in the queue. + */ + public int increment(int index) { + return (index == (elements.length - 1)) ? 0 : index + 1; + } + + public int indexOf(T target) { + if (tail >= head) { + for (int i = head; i < tail; i++) + if (target.equals(elements[i])) + return i; + } else { + for (int i = head; i < elements.length; i++) + if (target.equals(elements[i])) + return i; + for (int i = 0; i < tail; i++) + if (target.equals(elements[i])) + return i; + } + return -1; + } + + public boolean isEmpty() { + return tail == head; + } + + public T peek() { + return (T)elements[head]; + } + + public T peekTail() { + return (T)elements[decrement(tail)]; + } + + public T remove() { + if (isEmpty()) + return null; + T result = peek(); + if (!reuse) + elements[head] = null; + head = increment(head); + return result; + } + + public T removeTail() { + T result = peekTail(); + tail = decrement(tail); + if (!reuse) + elements[tail] = null; + return result; + } + + public void reset() { + tail = head = 0; + } + + public int size() { + return tail > head ? (tail - head) : ((elements.length - head) + tail); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + int count = 0; + if (!isEmpty()) { + Iterator it = iterator(); + //only print a fixed number of elements to prevent debugger from choking + while (count < 100) { + sb.append(it.next()); + if (it.hasNext()) + sb.append(',').append(' '); + else + break; + } + } + if (count < size()) + sb.append('.').append('.').append('.'); + sb.append(']'); + return sb.toString(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java new file mode 100644 index 0000000000..3b272ee9c2 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import java.util.HashMap; + +/** + * A string pool is used for sharing strings in a way that eliminates duplicate + * equal strings. A string pool instance can be maintained over a long period + * of time, or used as a temporary structure during a string sharing pass over + * a data structure. + *

                  + * This class is not intended to be subclassed by clients. + *

                  + * + * @see IStringPoolParticipant + * @since 3.1 + */ +public final class StringPool { + private int savings; + private final HashMap map = new HashMap<>(); + + /** + * Creates a new string pool. + */ + public StringPool() { + super(); + } + + /** + * Adds a String to the pool. Returns a String + * that is equal to the argument but that is unique within this pool. + * @param string The string to add to the pool + * @return A string that is equal to the argument. + */ + public String add(String string) { + if (string == null) + return string; + Object result = map.get(string); + if (result != null) { + if (result != string) + savings += 44 + 2 * string.length(); + return (String) result; + } + map.put(string, string); + return string; + } + + /** + * Returns an estimate of the size in bytes that was saved by sharing strings in + * the pool. In particular, this returns the size of all strings that were added to the + * pool after an equal string had already been added. This value can be used + * to estimate the effectiveness of a string sharing operation, in order to + * determine if or when it should be performed again. + * + * In some cases this does not precisely represent the number of bytes that + * were saved. For example, say the pool already contains string S1. Now + * string S2, which is equal to S1 but not identical, is added to the pool five + * times. This method will return the size of string S2 multiplied by the + * number of times it was added, even though the actual savings in this case + * is only the size of a single copy of S2. + */ + public int getSavedStringCount() { + return savings; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java new file mode 100644 index 0000000000..a743542614 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * 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.utils; + +import java.util.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.*; +import org.osgi.framework.Bundle; + +/** + * Performs string sharing passes on all string pool participants registered + * with the platform. + */ +public class StringPoolJob extends Job { + private static final long INITIAL_DELAY = 10000;//ten seconds + private static final long RESCHEDULE_DELAY = 300000;//five minutes + private long lastDuration; + /** + * Stores all registered string pool participants, along with the scheduling + * rule required when running it. + */ + private Map participants = Collections.synchronizedMap(new HashMap(10)); + + private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$ + + public StringPoolJob() { + super(Messages.utils_stringJobName); + setSystem(true); + setPriority(DECORATE); + } + + /** + * Adds a string pool participant. The job periodically builds + * a string pool and asks all registered participants to share their strings in + * the pool. Once all participants have added their strings to the pool, the + * pool is discarded to avoid additional memory overhead. + * + * Adding a participant that is equal to a participant already registered will + * replace the scheduling rule associated with the participant, but will otherwise + * be ignored. + * + * @param participant The participant to add + * @param rule The scheduling rule that must be owned at the time the + * participant is called. This allows a participant to protect their data structures + * against access at unsafe times. + * + * @see #removeStringPoolParticipant(IStringPoolParticipant) + * @since 3.1 + */ + public void addStringPoolParticipant(IStringPoolParticipant participant, ISchedulingRule rule) { + participants.put(participant, rule); + if (getState() == Job.SLEEPING) + wakeUp(INITIAL_DELAY); + else + schedule(INITIAL_DELAY); + } + + /** + * Removes the indicated log listener from the set of registered string + * pool participants. If no such participant is registered, no action is taken. + * + * @param participant the participant to deregister + * @see #addStringPoolParticipant(IStringPoolParticipant, ISchedulingRule) + * @since 3.1 + */ + public void removeStringPoolParticipant(IStringPoolParticipant participant) { + participants.remove(participant); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + //if the system is shutting down, don't build + if (systemBundle.getState() == Bundle.STOPPING) + return Status.OK_STATUS; + + //copy current participants to handle concurrent additions and removals to map + Map.Entry[] entries = participants.entrySet().toArray(new Map.Entry[participants.size()]); + ISchedulingRule[] rules = new ISchedulingRule[entries.length]; + IStringPoolParticipant[] toRun = new IStringPoolParticipant[entries.length]; + for (int i = 0; i < toRun.length; i++) { + toRun[i] = entries[i].getKey(); + rules[i] = entries[i].getValue(); + } + final ISchedulingRule rule = MultiRule.combine(rules); + long start = -1; + int savings = 0; + final IJobManager jobManager = Job.getJobManager(); + try { + jobManager.beginRule(rule, monitor); + start = System.currentTimeMillis(); + savings = shareStrings(toRun, monitor); + } finally { + jobManager.endRule(rule); + } + if (start > 0) { + lastDuration = System.currentTimeMillis() - start; + if (Policy.DEBUG_STRINGS) + Policy.debug("String sharing saved " + savings + " bytes in: " + lastDuration); //$NON-NLS-1$ //$NON-NLS-2$ + } + //throttle frequency if it takes too long + long scheduleDelay = Math.max(RESCHEDULE_DELAY, lastDuration * 100); + if (Policy.DEBUG_STRINGS) + Policy.debug("Rescheduling string sharing job in: " + scheduleDelay); //$NON-NLS-1$ + schedule(scheduleDelay); + return Status.OK_STATUS; + } + + private int shareStrings(IStringPoolParticipant[] toRun, IProgressMonitor monitor) { + final StringPool pool = new StringPool(); + for (int i = 0; i < toRun.length; i++) { + if (monitor.isCanceled()) + break; + final IStringPoolParticipant current = toRun[i]; + SafeRunner.run(new ISafeRunnable() { + @Override + public void handleException(Throwable exception) { + //exceptions are already logged, so nothing to do + } + + @Override + public void run() { + current.shareStrings(pool); + } + }); + } + return pool.getSavedStringCount(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java new file mode 100644 index 0000000000..2213745b18 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java @@ -0,0 +1,359 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Mickael Istria (Red Hat Inc.) - Bug 488938, 488937 + *******************************************************************************/ +package org.eclipse.core.internal.utils; + +import java.io.*; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.util.GregorianCalendar; +import java.util.Random; +import org.eclipse.core.runtime.Assert; + +public class UniversalUniqueIdentifier implements java.io.Serializable { + + /** + * All serializable objects should have a stable serialVersionUID + */ + private static final long serialVersionUID = 1L; + + /* INSTANCE FIELDS =============================================== */ + + private byte[] fBits = new byte[BYTES_SIZE]; + + /* NON-FINAL PRIVATE STATIC FIELDS =============================== */ + + private static BigInteger fgPreviousClockValue; + private static int fgClockAdjustment = 0; + private static int fgClockSequence = -1; + private static byte[] nodeAddress; + + static { + nodeAddress = computeNodeAddress(); + } + + /* PRIVATE STATIC FINAL FIELDS =================================== */ + + private static Random fgRandomNumberGenerator = new Random(); + + /* PUBLIC STATIC FINAL FIELDS ==================================== */ + + public static final int BYTES_SIZE = 16; + public static final byte[] UNDEFINED_UUID_BYTES = new byte[16]; + public static final int MAX_CLOCK_SEQUENCE = 0x4000; + public static final int MAX_CLOCK_ADJUSTMENT = 0x7FFF; + public static final int TIME_FIELD_START = 0; + public static final int TIME_FIELD_STOP = 6; + public static final int TIME_HIGH_AND_VERSION = 7; + public static final int CLOCK_SEQUENCE_HIGH_AND_RESERVED = 8; + public static final int CLOCK_SEQUENCE_LOW = 9; + public static final int NODE_ADDRESS_START = 10; + public static final int NODE_ADDRESS_BYTE_SIZE = 6; + + public static final int BYTE_MASK = 0xFF; + + public static final int HIGH_NIBBLE_MASK = 0xF0; + + public static final int LOW_NIBBLE_MASK = 0x0F; + + public static final int SHIFT_NIBBLE = 4; + + public static final int ShiftByte = 8; + + /** + UniversalUniqueIdentifier default constructor returns a + new instance that has been initialized to a unique value. + */ + public UniversalUniqueIdentifier() { + this.setVersion(1); + this.setVariant(1); + this.setTimeValues(); + this.setNode(getNodeAddress()); + } + + /** + Constructor that accepts the bytes to use for the instance.   The format + of the byte array is compatible with the toBytes() method. + +

                  The constructor returns the undefined uuid if the byte array is invalid. + + @see #toBytes() + @see #BYTES_SIZE + */ + public UniversalUniqueIdentifier(byte[] byteValue) { + fBits = new byte[BYTES_SIZE]; + if (byteValue.length >= BYTES_SIZE) + System.arraycopy(byteValue, 0, fBits, 0, BYTES_SIZE); + } + + private void appendByteString(StringBuilder 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); + } + + private static BigInteger clockValueNow() { + GregorianCalendar now = new GregorianCalendar(); + BigInteger nowMillis = BigInteger.valueOf(now.getTime().getTime()); + BigInteger baseMillis = BigInteger.valueOf(now.getGregorianChange().getTime()); + + return (nowMillis.subtract(baseMillis).multiply(BigInteger.valueOf(10000L))); + } + + /** + Simply increases the visibility of Object's clone. + Otherwise, no new behaviour. + */ + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + Assert.isTrue(false, Messages.utils_clone); + return null; + } + } + + public static int compareTime(byte[] fBits1, byte[] fBits2) { + for (int i = TIME_FIELD_STOP; i >= 0; i--) + if (fBits1[i] != fBits2[i]) + return (0xFF & fBits1[i]) - (0xFF & fBits2[i]); + return 0; + } + + /** + * Answers the node address attempting to mask the IP + * address of this machine. + * + * @return byte[] the node address + */ + private static byte[] computeNodeAddress() { + + byte[] address = new byte[NODE_ADDRESS_BYTE_SIZE]; + + // Seed the secure randomizer with some oft-varying inputs + int thread = Thread.currentThread().hashCode(); + long time = System.currentTimeMillis(); + int objectId = System.identityHashCode(""); //$NON-NLS-1$ + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(byteOut); + byte[] ipAddress = getIPAddress(); + + try { + if (ipAddress != null) + out.write(ipAddress); + out.write(thread); + out.writeLong(time); + out.write(objectId); + out.close(); + } catch (IOException exc) { + //ignore the failure, we're just trying to come up with a random seed + } + byte[] rand = byteOut.toByteArray(); + + SecureRandom randomizer = new SecureRandom(rand); + randomizer.nextBytes(address); + + // set the MSB of the first octet to 1 to distinguish from IEEE node addresses + address[0] = (byte) (address[0] | (byte) 0x80); + + return address; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof UniversalUniqueIdentifier)) + return false; + + byte[] other = ((UniversalUniqueIdentifier) obj).fBits; + if (fBits == other) + return true; + if (fBits.length != other.length) + return false; + for (int i = 0; i < fBits.length; i++) { + if (fBits[i] != other[i]) + return false; + } + return true; + } + + /** + Answers the IP address of the local machine using the + Java API class InetAddress. + + @return byte[] the network address in network order + @see java.net.InetAddress#getLocalHost() + @see java.net.InetAddress#getAddress() + */ + protected static byte[] getIPAddress() { + try { + return InetAddress.getLocalHost().getAddress(); + } catch (UnknownHostException e) { + //valid for this to be thrown be a machine with no IP connection + //It is VERY important NOT to throw this exception + return null; + } catch (ArrayIndexOutOfBoundsException e) { + // there appears to be a bug in the VM if there is an alias + // see bug 354820. As above it is important not to throw this + return null; + } + } + + private static byte[] getNodeAddress() { + return nodeAddress; + } + + @Override + public int hashCode() { + return fBits[0] + fBits[3] + fBits[7] + fBits[11] + fBits[15]; + } + + private static int nextClockSequence() { + + if (fgClockSequence == -1) + fgClockSequence = (int) (fgRandomNumberGenerator.nextDouble() * MAX_CLOCK_SEQUENCE); + + fgClockSequence = (fgClockSequence + 1) % MAX_CLOCK_SEQUENCE; + + return fgClockSequence; + } + + private static BigInteger nextTimestamp() { + + BigInteger timestamp = clockValueNow(); + int timestampComparison; + + timestampComparison = timestamp.compareTo(fgPreviousClockValue); + + if (timestampComparison == 0) { + if (fgClockAdjustment == MAX_CLOCK_ADJUSTMENT) { + while (timestamp.compareTo(fgPreviousClockValue) == 0) + timestamp = clockValueNow(); + timestamp = nextTimestamp(); + } else + fgClockAdjustment++; + } else { + fgClockAdjustment = 0; + + if (timestampComparison < 0) + nextClockSequence(); + } + + return timestamp; + } + + private void setClockSequence(int clockSeq) { + int clockSeqHigh = (clockSeq >>> ShiftByte) & LOW_NIBBLE_MASK; + int reserved = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & HIGH_NIBBLE_MASK; + + fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) (reserved | clockSeqHigh); + fBits[CLOCK_SEQUENCE_LOW] = (byte) (clockSeq & BYTE_MASK); + } + + protected void setNode(byte[] bytes) { + + for (int index = 0; index < NODE_ADDRESS_BYTE_SIZE; index++) + fBits[index + NODE_ADDRESS_START] = bytes[index]; + } + + private void setTimestamp(BigInteger timestamp) { + BigInteger value = timestamp; + BigInteger bigByte = BigInteger.valueOf(256L); + BigInteger[] results; + int version; + int timeHigh; + + for (int index = TIME_FIELD_START; index < TIME_FIELD_STOP; index++) { + results = value.divideAndRemainder(bigByte); + value = results[0]; + fBits[index] = (byte) results[1].intValue(); + } + version = fBits[TIME_HIGH_AND_VERSION] & HIGH_NIBBLE_MASK; + timeHigh = value.intValue() & LOW_NIBBLE_MASK; + fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | version); + } + + protected synchronized void setTimeValues() { + this.setTimestamp(timestamp()); + this.setClockSequence(fgClockSequence); + } + + protected int setVariant(int variantIdentifier) { + int clockSeqHigh = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & LOW_NIBBLE_MASK; + int variant = variantIdentifier & LOW_NIBBLE_MASK; + + fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) ((variant << SHIFT_NIBBLE) | clockSeqHigh); + return (variant); + } + + protected void setVersion(int versionIdentifier) { + int timeHigh = fBits[TIME_HIGH_AND_VERSION] & LOW_NIBBLE_MASK; + int version = versionIdentifier & LOW_NIBBLE_MASK; + + fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | (version << SHIFT_NIBBLE)); + } + + private static BigInteger timestamp() { + BigInteger timestamp; + + if (fgPreviousClockValue == null) { + fgClockAdjustment = 0; + nextClockSequence(); + timestamp = clockValueNow(); + } else + timestamp = nextTimestamp(); + + fgPreviousClockValue = timestamp; + return fgClockAdjustment == 0 ? timestamp : timestamp.add(BigInteger.valueOf(fgClockAdjustment)); + } + + /** + This representation is compatible with the (byte[]) constructor. + + @see #UniversalUniqueIdentifier(byte[]) + */ + public byte[] toBytes() { + byte[] result = new byte[fBits.length]; + + System.arraycopy(fBits, 0, result, 0, fBits.length); + return result; + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < fBits.length; i++) + appendByteString(buffer, fBits[i]); + return buffer.toString(); + } + + public String toStringAsBytes() { + StringBuilder result = new StringBuilder("{"); //$NON-NLS-1$ + + for (int i = 0; i < fBits.length; i++) { + result.append(fBits[i]); + if (i < fBits.length + 1) + result.append(','); + } + result.append('}'); + return result.toString(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java new file mode 100644 index 0000000000..1d8e475408 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.utils; + +public class WrappedRuntimeException extends RuntimeException { + + /** + * All serializable objects should have a stable serialVersionUID + */ + private static final long serialVersionUID = 1L; + + private Throwable target; + + public WrappedRuntimeException(Throwable target) { + super(); + this.target = target; + } + + public Throwable getTargetException() { + return this.target; + } + + @Override + public String getMessage() { + return target.getMessage(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties new file mode 100644 index 0000000000..eadd09cc6a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties @@ -0,0 +1,327 @@ +############################################################################### +# Copyright (c) 2000, 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 +# Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering +# Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support +# Francis Lynch (Wind River) - [301563] Save and load tree snapshots +# Martin Oberhuber (Wind River) - [306575] Save snapshot location with project +# Mikael Barbero (Eclipse Foundation) - [286681] handle WAIT_ABANDONED_0 return value +############################################################################### +### Resources plugin messages. + +### dtree +dtree_immutable = Illegal attempt to modify an immutable tree. +dtree_malformedTree = Malformed tree. +dtree_missingChild = Missing child node: {0}. +dtree_notFound = Tree element ''{0}'' not found. +dtree_notImmutable = Tree must be immutable. +dtree_reverse = Tried to reverse a non-comparison tree. +dtree_subclassImplement = Subclass should have implemented this. +dtree_switchError = Switch error in DeltaTreeReader.readNode(). + +### events +events_builderError = Errors running builder ''{0}'' on project ''{1}''. +events_building_0 = Building workspace +events_building_1 = Building ''{0}'' +events_errors = Errors occurred during the build. +events_instantiate_1 = Error instantiating builder ''{0}''. +events_invoking_1 = Invoking builder on ''{0}''. +events_invoking_2 = Invoking ''{0}'' on ''{1}''. +events_skippingBuilder = Skipping builder ''{0}'' for project ''{1}''. Either the builder is missing from the install, or it belongs to a project nature that is missing or disabled. +events_unknown = {0} encountered while running {1}. + +history_copyToNull = Unable to copy local history to or from a null location. +history_copyToSelf = Unable to copy local history to and from the same location. +history_errorContentDescription = Error retrieving content description for local history for: ''{0}''. +history_notValid = State is not valid or might have expired. +history_problemsCleaning = Problems cleaning up history store. + +links_creating = Creating link. +links_errorLinkReconcile = Error processing changed links in project description file. +links_invalidLocation = ''{0}'' is not a valid location for linked resources. +links_localDoesNotExist = Cannot create linked resource. Local location ''{0}'' does not exist. +links_locationOverlapsLink = ''{0}'' is not a valid location because the project contains a linked resource at that location. +links_locationOverlapsProject = Cannot create a link to ''{0}'' because it overlaps the location of the project that contains the linked resource. +links_natureVeto = Linking is not allowed because project nature ''{0}'' does not allow it. +links_noPath = A Link location must be specified. +links_overlappingResource = Location ''{0}'' may overlap another resource. This can cause unexpected side-effects. +links_updatingDuplicate = Updating duplicate resource: ''{0}''. +links_parentNotAccessible = Cannot create linked resource ''{0}''. The parent resource is not accessible. +links_notFileFolder = Cannot create linked resource ''{0}''. Only files and folders can be linked. +links_vetoNature = Cannot add nature because project ''{0}'' contains linked resources, and nature ''{1}'' does not allow it. +links_workspaceVeto = Linked resources are not supported by this application. +links_wrongLocalType = Cannot create linked resource ''{0}''. Files cannot be linked to folders. +links_resourceIsNotALink=Resource ''{0}'' must be a linked resource to change its linked location. +links_setLocation=Changing link location. + +group_invalidParent = Only virtual folders and links can be created under a virtual folder. + +filters_missingFilterType= Missing resource filter type: ''{0}''. + +### local store +localstore_copying = Copying ''{0}''. +localstore_copyProblem = Problems encountered while copying resources. +localstore_couldnotDelete = Could not delete ''{0}''. +localstore_couldNotMove = Could not move ''{0}''. +localstore_couldNotRead = Could not read file ''{0}''. +localstore_couldNotWrite = Could not write file ''{0}''. +localstore_couldNotWriteReadOnly = Could not write to read-only file: ''{0}''. +localstore_deleteProblem = Problems encountered while deleting resources. +localstore_deleting = Deleting ''{0}''. +localstore_failedReadDuringWrite = Could not read from source when writing file ''{0}'' +localstore_fileExists = A resource already exists on disk ''{0}''. +localstore_fileNotFound = File not found: {0}. +localstore_locationUndefined = The location for ''{0}'' could not be determined because it is based on an undefined path variable. +localstore_refreshing = Refreshing ''{0}''. +localstore_refreshingRoot = Refreshing workspace. +localstore_resourceExists = Resource already exists on disk: ''{0}''. +localstore_resourceDoesNotExist = Resource does not exist on disk: ''{0}''. +localstore_resourceIsOutOfSync = Resource is out of sync with the file system: ''{0}''. + +### Resource mappings and models +mapping_invalidDef = Model provider extension found with invalid definition: {0}. +mapping_wrongType = Model provider ''{0}'' does not extend ModelProvider. +mapping_noIdentifier = Found model provider extension with no identifier; ignoring extension. +mapping_validate = Validating resource changes +mapping_multiProblems = Multiple potential side effects have been identified. + +### internal.resources +natures_duplicateNature = Duplicate nature: {0}. +natures_hasCycle = Nature is invalid because its prerequisites form a cycle: {0} +natures_missingIdentifier = Found nature extension with no identifier; ignoring extension. +natures_missingNature = Nature does not exist: {0}. +natures_missingPrerequisite = Nature {0} is missing prerequisite nature: {1}. +natures_multipleSetMembers = Multiple natures found for nature set: {0}. +natures_invalidDefinition = Nature extension found with invalid definition: {0}. +natures_invalidRemoval = Cannot remove nature {0} because it is a prerequisite of nature {1}. +natures_invalidSet = The set of natures is not valid. + +pathvar_length = Path variable name must have a length > 0. +pathvar_beginLetter = Path variable name must begin with a letter or underscore. +pathvar_invalidChar = Path variable name cannot contain character: {0}. +pathvar_invalidValue = Path variable value must be valid and absolute. +pathvar_undefined = ''{0}'' is not a valid location. The location is relative to undefined workspace path variable ''{1}''. +pathvar_whitespace= Path variable name cannot contain whitespace + +### preferences +preferences_deleteException=Exception deleting file: {0}. +preferences_loadException=Exception loading preferences from: {0}. +preferences_operationCanceled=Operation canceled. +preferences_removeNodeException=Exception while removing preference node: {0}. +preferences_clearNodeException=Exception while clearing preference node: {0}. +preferences_saveProblems=Exception occurred while saving project preferences: {0}. +preferences_syncException=Exception occurred while synchronizing node: {0}. + +projRead_badLinkName = Names ''{0}'' and ''{1}'' detected for a single link. Using ''{0}''. +projRead_badLinkType2 = Types ''{0}'' and ''{1}'' detected for a single link. Using ''{0}''. +projRead_badLocation = Locations ''{0}'' and ''{1}'' detected for a single link. Using ''{0}''. +projRead_emptyLinkName = Empty name detected for linked resource with type ''{0}'' and location ''{1}''. +projRead_badLinkType = Illegal link type \"-1\" detected for linked resource with name ''{0}'' and location ''{1}''. +projRead_badLinkLocation = Empty location detected for linked resource with name ''{0}'' and type ''{1}''. +projRead_whichKey = Two values detected for an argument name in a build command: ''{0}'' and ''{1}''. Using ''{0}''. +projRead_whichValue = Two values detected for an argument value in a build command: ''{0}'' and ''{1}''. Using ''{0}''. +projRead_notProjectDescription = Encountered element name ''{0}'' instead of \"project\" when trying to read a project description file. +projRead_badSnapshotLocation = Invalid resource snapshot location URI ''{0}'' is not absolute. +projRead_cannotReadSnapshot = Failed to read resource snapshot for project ''{0}'': {1} +projRead_failureReadingProjectDesc = Failure occurred reading .project file. +projRead_missingProjectName = Missing project name. + +projRead_emptyVariableName = Empty variable name detected in project "{0}\". + +projRead_badFilterName = Names ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. +projRead_emptyFilterName = Empty name detected for filtered resource with type ''{0}'' and id ''{1}''. +projRead_badFilterID = Empty filter id detected for filtered resource with name ''{0}'' and type ''{1}''. +projRead_badFilterType = Illegal filter type \"-1\" detected for filtered resource with name ''{0}'' and id ''{1}''. +projRead_badFilterType2 = Types ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. +projRead_badID = IDs ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. +projRead_badArguments= Arguments ''{0}'' and ''{1}'' detected for a single filter. Using ''{0}''. + +properties_qualifierIsNull = Qualifier part of property key cannot be null. +properties_readProperties = Failure while reading persistent properties for resource ''{0}'', file was corrupt. Some properties may have been lost. +properties_valueTooLong = Could not set property: {0} {1}. Value is too long. +properties_couldNotClose = Could not close property store for: {0}. + +### auto-refresh +refresh_jobName = Refreshing workspace +refresh_task = Resources to refresh: {0} +refresh_pollJob = Searching for local changes +refresh_refreshErr = Problems occurred while refreshing local changes +refresh_restoreOnInvalid = Restore auto-refresh monitors on invalid resources +refresh_installError = An error occurred while installing an auto-refresh monitor +refresh_installMonitorsOnWorkspace = Installing auto-refresh monitors on the workspace +refresh_uninstallMonitorsOnWorkspace = Uninstalling auto-refresh monitors from the workspace + +resources_cannotModify = The resource tree is locked for modifications. +resources_changeInAdd = Trying to change a marker in an add method. +resources_charsetBroadcasting = Reporting encoding changes. +resources_charsetUpdating = Updating encoding settings. +resources_closing_0 = Closing workspace. +resources_closing_1 = Closing ''{0}''. +resources_copyDestNotSub = Cannot copy ''{0}''. Destination should not be under source''s hierarchy. +resources_copying = Copying ''{0}''. +resources_copying_0 = Copying. +resources_copyNotMet = Copy requirements not met. +resources_copyProblem = Problems encountered while copying resources. +resources_couldnotDelete = Could not delete ''{0}''. +resources_create = Create. +resources_creating = Creating resource ''{0}''. +resources_deleteMeta = Could not delete metadata for ''{0}''. +resources_deleteProblem = Problems encountered while deleting resources. +resources_deleting = Deleting ''{0}''. +resources_deleting_0 = Deleting. +resources_destNotNull = Destination path should not be null. +resources_errorContentDescription = Error retrieving content description for resource ''{0}''. +resources_errorDeleting = Error deleting resource ''{0}'' from the workspace tree. +resources_errorMarkersDelete = Error deleting markers for resource ''{0}''. +resources_errorMarkersMove = Error moving markers from resource ''{0}'' to ''{1}''. +resources_wrongMarkerAttributeValueType = "The attribute value type is {0} and expected is one of java.lang.String, Boolean, Integer" +resources_errorMembers = Error retrieving members of container ''{0}''. +resources_errorMoving = Error moving resource ''{0}'' to ''{1}'' in the workspace tree. +resources_errorNature = Error configuring nature ''{0}''. +resources_errorPropertiesMove = Error moving properties for resource ''{0}'' to ''{1}''. +resources_errorRefresh = Errors occurred during refresh of resource ''{0}''. +resources_errorReadProject = Failed to read project description file from location ''{0}''. +resources_errorMultiRefresh = Errors occurred while refreshing resources with the local file system. +resources_errorValidator = Exception running validator code. +resources_errorVisiting = An error occurred while traversing resources. +resources_existsDifferentCase = A resource exists with a different case: ''{0}''. +resources_existsLocalDifferentCase = A resource exists on disk with a different case: ''{0}''. +resources_exMasterTable = Could not read master table. +resources_exReadProjectLocation = Could not read the project location for ''{0}''. +resources_exSafeRead = Could not read safe table. +resources_exSafeSave = Could not save safe table. +resources_exSaveMaster = Could not save master table to file ''{0}''. +resources_exSaveProjectLocation = Could not save the project location for ''{0}''. +resources_fileExists = A resource already exists on disk ''{0}''. +resources_fileToProj = Cannot copy a file to a project. +resources_flushingContentDescriptionCache = Flushing content description cache. +resources_folderOverFile = Cannot overwrite folder with file ''{0}''. +resources_format = Incompatible file format. Workspace was saved with an incompatible version: {0}. +resources_initValidator = Unable to instantiate validator. +resources_initHook = Unable to instantiate move/delete hook. +resources_initTeamHook = Unable to instantiate team hook. +resources_invalidCharInName = {0} is an invalid character in resource name ''{1}''. +resources_invalidCharInPath = {0} is an invalid character in path ''{1}''. +resources_invalidName = ''{0}'' is an invalid name on this platform. +resources_invalidPath = ''{0}'' is an invalid resource path. +resources_invalidProjDesc = Invalid project description. +resources_invalidResourceName = ''{0}'' is an invalid resource name. +resources_invalidRoot = Root (/) is an invalid resource path. +resources_markerNotFound = Marker id {0} not found. +resources_missingProjectMeta = The project description file (.project) for ''{0}'' is missing. This file contains important information about the project. The project will not function properly until this file is restored. +resources_missingProjectMetaRepaired = The project description file (.project) for ''{0}'' was missing. This file contains important information about the project. A new project description file has been created, but some information about the project may have been lost. +resources_moveDestNotSub = Cannot move ''{0}''. Destination should not be under source''s hierarchy. +resources_moveMeta = Error moving metadata area from {0} to {1}. +resources_moveNotMet = Move requirements not met. +resources_moveNotProject = Cannot move ''{0}'' to ''{1}''. Source must be a project. +resources_moveProblem = Problems encountered while moving resources. +resources_moveRoot = Cannot move the workspace root. +resources_moving = Moving ''{0}''. +resources_moving_0 = Moving. +resources_mustBeAbsolute = Path ''{0}'' must be absolute. +resources_mustBeLocal = Resource ''{0}'' is not local. +resources_mustBeOpen = Project ''{0}'' is not open. +resources_mustExist = Resource ''{0}'' does not exist. +resources_mustNotExist = Resource ''{0}'' already exists. +resources_nameEmpty = Name cannot be empty. +resources_nameNull = Name must not be null. +resources_natureClass = Missing project nature class for ''{0}''. +resources_natureDeconfig = Error deconfiguring nature: {0}. +resources_natureExtension = Missing project nature extension for {0}. +resources_natureFormat = Project nature {0} does not specify a runtime attribute. +resources_natureImplement = Project nature {0} does not implement IProjectNature. +resources_notChild = Resource ''{0}'' is not a child of ''{1}''. +resources_oneValidator = There must be exactly 0 or 1 validator extensions defined in the fileModificationValidator extension point. +resources_oneHook = There must be exactly 0 or 1 hook extensions defined in the moveDeleteHook extension point. +resources_oneTeamHook = There must be exactly 0 or 1 hook extensions defined in the teamHook extension point. +resources_opening_1 = Opening ''{0}''. +resources_overlapWorkspace = {0} overlaps the workspace location: {1} +resources_overlapProject = {0} overlaps the location of another project: ''{1}'' +resources_pathNull = Paths must not be null. +resources_projectDesc = Problems encountered while setting project description. +resources_projectDescSync = Could not set the project description for ''{0}'' because the project description file (.project) is out of sync with the file system. +resources_projectMustNotBeOpen = Project must not be open. +resources_projectPath = Path for project must have only one segment. +resources_pruningHistory = Pruning history. +resources_reading = Reading. +resources_readingSnap = Reading snapshot. +resources_readingEncoding = Could not read encoding settings. +resources_readMarkers = Failure while reading markers, the marker file was corrupt. Some markers may be lost. +resources_readMeta = Could not read metadata for ''{0}''. +resources_readMetaWrongVersion = Could not read metadata for ''{0}''. Unexpected version: {1}. +resources_readOnly = Resource ''{0}'' is read-only. +resources_readOnly2 = Cannot edit read-only resources. +resources_readProjectMeta = Failed to read the project description file (.project) for ''{0}''. The file has been changed on disk, and it now contains invalid information. The project will not function properly until the description file is restored to a valid state. +resources_readProjectTree = Problems reading project tree. +resources_readSync = Errors reading sync info file: {0}. +resources_readWorkspaceMeta = Could not read workspace metadata. +resources_readWorkspaceMetaValue = Invalid attribute value in workspace metadata: {0}. Value will be ignored. +resources_readWorkspaceSnap = Problems reading workspace tree snapshot. +resources_readWorkspaceTree = Problems reading workspace tree. +resources_refreshing = Refreshing ''{0}''. +resources_refreshingRoot = Refreshing workspace. +resources_resetMarkers = Could not reset markers snapshot file. +resources_resetSync = Could not reset sync info snapshot file. +resources_resourcePath = Invalid path for resource ''{0}''. Must include project and resource name. +resources_saveOp = Save cannot be called from inside an operation. +resources_saveProblem = Problems occurred during save. +resources_saveWarnings = Save operation warnings. +resources_saving_0 = Saving workspace. +resources_savingEncoding = Could not save encoding settings. +resources_setDesc = Setting project description. +resources_setLocal = Setting resource local flag. +resources_settingCharset = Setting character set for resource ''{0}''. +resources_settingDefaultCharsetContainer = Setting default character set for resource ''{0}''. +resources_settingContents = Setting contents for ''{0}''. +resources_settingDerivedFlag = Setting derived flag for resource ''{0}''. +resources_shutdown = Workspace was not properly initialized or has already shutdown. +resources_shutdownProblems = Problem on shutdown. +resources_snapInit = Could not initialize snapshot file. +resources_snapRead = Could not read snapshot file. +resources_snapRequest = Snapshot requested. +resources_snapshot = Periodic workspace save. +resources_startupProblems = Workspace restored, but some problems occurred. +resources_touch = Touching resource ''{0}''. +resources_updating = Updating workspace +resources_updatingEncoding = Problems encountered while updating encoding settings. +resources_workspaceClosed = Workspace is closed. +resources_workspaceOpen = The workspace is already open. +resources_writeMeta = Could not write metadata for ''{0}''. +resources_writeWorkspaceMeta = Could not write workspace metadata ''{0}''. +resources_errorResourceIsFiltered=The resource will be filtered out by its parent resource filters + +synchronizer_partnerNotRegistered = Sync partner: {0} not registered with the synchronizer. + +### URL +url_badVariant = Unsupported \"platform:\" protocol variation {0}. +url_couldNotResolve_projectDoesNotExist = Project ''{0}'' does not exist. Could not resolve URL: {1}. +url_couldNotResolve_URLProtocolHandlerCanNotResolveURL = A protocol handler does not exist or can not resolve URL ''{0}'' into URL with file protocol. Could not resolve URL: {1}. +url_couldNotResolve_resourceLocationCanNotBeDetermined = Resource location ''{0}'' can not be determined. Could not resolve URL: {1}. + +### utils +utils_clone = Clone not supported. +utils_stringJobName = Compacting resource model + +### watson +watson_elementNotFound = Element not found: {0}. +watson_illegalSubtree = Illegal subtree passed to createSubtree(). +watson_immutable = Attempt to modify an immutable tree. +watson_noModify = Cannot modify implicit root node. +watson_nullArg = Null argument to {0}. +watson_unknown = Unknown format. + +### auto-refresh win32 native +WM_beginTask = finding out of sync resources +WM_jobName = Win32 refresh daemon +WM_errors = Problems occurred refreshing resources +WM_nativeErr = Problem occurred in auto-refresh native code: {0}. +WM_mutexAbandoned = Problem occurred in auto-refresh native code (WAIT_ABANDONED_0 has been returned for handle ''{0}''). +WM_errCloseHandle = Problem closing native refresh handle: {0}. +WM_errCreateHandle = Problem creating handle for {0}, code: {0}. +WM_errFindChange = Problem finding next change, code: {0} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java new file mode 100644 index 0000000000..f21af4e383 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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.watson; + +/** + * This is what you would expect for an element tree comparator. + * Clients of the element tree that want specific comparison behaviour + * must define their own element comparator (without subclassing or + * otherwise extending this comparator). Internal element tree operations + * rely on the behaviour of this type, and the ElementTree maintainer + * reserves the right to change its behaviour as necessary. + */ +public final class DefaultElementComparator implements IElementComparator { + private static DefaultElementComparator singleton; + + /** + * Force clients to use the singleton + */ + protected DefaultElementComparator() { + super(); + } + + /** + * Returns the type of change. + */ + @Override + public int compare(Object oldInfo, Object newInfo) { + if (oldInfo == null && newInfo == null) + return 0; + if (oldInfo == null || newInfo == null) + return 1; + return testEquality(oldInfo, newInfo) ? 0 : 1; + } + + /** + * Returns the singleton instance + */ + public static IElementComparator getComparator() { + if (singleton == null) { + singleton = new DefaultElementComparator(); + } + return singleton; + } + + /** + * Makes a comparison based on equality + */ + protected boolean testEquality(Object oldInfo, Object newInfo) { + if (oldInfo == null && newInfo == null) + return true; + if (oldInfo == null || newInfo == null) + return false; + + return oldInfo.equals(newInfo); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java new file mode 100644 index 0000000000..a997f5912e --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java @@ -0,0 +1,732 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +import java.util.HashMap; +import org.eclipse.core.internal.dtree.*; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.StringPool; +import org.eclipse.core.runtime.*; +import org.eclipse.osgi.util.NLS; + +/** + * An ElementTree can be viewed as a generic rooted tree that stores + * a hierarchy of elements. An element in the tree consists of a + * (name, data, children) 3-tuple. The name can be any String, and + * the data can be any Object. The children are a collection of zero + * or more elements that logically fall below their parent in the tree. + * The implementation makes no guarantees about the ordering of children. + * + * Elements in the tree are referenced by a key that consists of the names + * of all elements on the path from the root to that element in the tree. + * For example, if root node "a" has child "b", which has child "c", element + * "c" can be referenced in the tree using the key (/a/b/c). Keys are represented + * using IPath objects, where the Paths are relative to the root element of the + * tree. + * @see IPath + * + * Each ElementTree has a single root element that is created implicitly and + * is always present in any tree. This root corresponds to the key (/), + * or the singleton Path.ROOT. The root element cannot be created + * or deleted, and its data and name cannot be set. The root element's children + * however can be modified (added, deleted, etc). The root path can be obtained + * using the getRoot() method. + * + * ElementTrees are modified in generations. The method newEmptyDelta() + * returns a new tree generation that can be modified arbitrarily by the user. + * For the purpose of explanation, we call such a tree "active". + * When the method immutable() is called, that tree generation is + * frozen, and can never again be modified. A tree must be immutable before + * a new tree generation can start. Since all ancestor trees are immutable, + * different active trees can have ancestors in common without fear of + * thread corruption problems. + * + * Internally, any single tree generation is simply stored as the + * set of changes between itself and its most recent ancestor (its parent). + * This compact delta representation allows chains of element trees to + * be created at relatively low cost. Clients of the ElementTree can + * instantaneously "undo" sets of changes by navigating up to the parent + * tree using the getParent() method. + * + * Although the delta representation is compact, extremely long delta + * chains make for a large structure that is potentially slow to query. + * For this reason, the client is encouraged to minimize delta chain + * lengths using the collapsing(int) and makeComplete() + * methods. The getDeltaDepth() method can be used to + * discover the length of the delta chain. The entire delta chain can + * also be re-oriented in terms of the current element tree using the + * reroot() operation. + * + * Classes are also available for tree serialization and navigation. + * @see ElementTreeReader + * @see ElementTreeWriter + * @see ElementTreeIterator + * + * Finally, why are ElementTrees in a package called "watson"? + * - "It's ElementTree my dear Watson, ElementTree." + */ +public class ElementTree { + protected DeltaDataTree tree; + protected IElementTreeData userData; + + private class ChildIDsCache { + ChildIDsCache(IPath path, IPath[] childPaths) { + this.path = path; + this.childPaths = childPaths; + } + + IPath path; + IPath[] childPaths; + } + + private volatile ChildIDsCache childIDsCache = null; + + private volatile DataTreeLookup lookupCache = null; + + private volatile DataTreeLookup lookupCacheIgnoreCase = null; + + private static int treeCounter = 0; + private int treeStamp; + + /** + * Creates a new empty element tree. + */ + public ElementTree() { + initialize(new DeltaDataTree()); + } + + /** + * Creates an element tree given its internal node representation. + */ + protected ElementTree(DataTreeNode rootNode) { + initialize(rootNode); + } + + /** + * Creates a new element tree with the given data tree as its representation. + */ + protected ElementTree(DeltaDataTree tree) { + initialize(tree); + } + + /** + * Creates a new empty delta element tree having the + * given tree as its parent. + */ + protected ElementTree(ElementTree parent) { + if (!parent.isImmutable()) { + parent.immutable(); + } + + /* copy the user data forward */ + IElementTreeData data = parent.getTreeData(); + if (data != null) { + userData = (IElementTreeData) data.clone(); + } + + initialize(parent.tree.newEmptyDeltaTree()); + } + + /** + * 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 + * element trees created by newEmptyDelta()/immutable(). + * + *

                  This element tree must be immutable at the start of this operation, + * and will be immutable afterwards. + * @return this tree. + */ + public synchronized ElementTree collapseTo(ElementTree parent) { + Assert.isTrue(tree.isImmutable()); + if (this == parent) { + //already collapsed + return this; + } + //collapse my tree to be a forward delta of the parent's tree. + tree.collapseTo(parent.tree, DefaultElementComparator.getComparator()); + return this; + } + + /** + * Creates the indicated element and sets its element info. + * The parent element must be present, otherwise an IllegalArgumentException + * is thrown. If the indicated element is already present in the tree, + * its element info is replaced and any existing children are + * deleted. + * + * @param key element key + * @param data element data, or null + */ + public synchronized void createElement(IPath key, Object data) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return; + + // Clear the child IDs cache in case it's referring to this parent. This is conservative. + childIDsCache = null; + + IPath parent = key.removeLastSegments(1); + try { + tree.createChild(parent, key.lastSegment(), data); + } catch (ObjectNotFoundException e) { + elementNotFound(parent); + } + // Set the lookup to be this newly created object. + lookupCache = DataTreeLookup.newLookup(key, true, data, true); + lookupCacheIgnoreCase = null; + } + + /** + * Creates or replaces the subtree below the given path with + * the given tree. The subtree can only have one child below + * the root, which will become the node specified by the given + * key in this tree. + * + * @param key The path of the new subtree in this tree. + * @see #getSubtree(IPath) + */ + public synchronized void createSubtree(IPath key, ElementTree subtree) { + /* don't allow creating subtrees at the root */ + if (key.isRoot()) { + throw new IllegalArgumentException(Messages.watson_noModify); + } + + // Clear the child IDs cache in case it's referring to this parent. + // This is conservative. + childIDsCache = null; + // Clear the lookup cache, in case the element being created is the same + // as for the last lookup. + lookupCache = lookupCacheIgnoreCase = null; + try { + /* don't copy the implicit root node of the subtree */ + IPath[] children = subtree.getChildren(subtree.getRoot()); + if (children.length != 1) { + throw new IllegalArgumentException(Messages.watson_illegalSubtree); + } + + /* get the subtree for the specified key */ + DataTreeNode node = (DataTreeNode) subtree.tree.copyCompleteSubtree(children[0]); + + /* insert the subtree in this tree */ + tree.createSubtree(key, node); + + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + + /** + * Deletes the indicated element and its descendents. + * The element must be present. + */ + public synchronized void deleteElement(IPath key) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return; + + // Clear the child IDs cache in case it's referring to this parent. + // This is conservative. + childIDsCache = null; + // Clear the lookup cache, in case the element being deleted is the same + // as for the last lookup. + lookupCache = lookupCacheIgnoreCase = null; + try { + tree.deleteChild(key.removeLastSegments(1), key.lastSegment()); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + + /** + * Complains that an element was not found + */ + protected void elementNotFound(IPath key) { + throw new IllegalArgumentException(NLS.bind(Messages.watson_elementNotFound, key)); + } + + /** + * Given an array of element trees, returns the index of the + * oldest tree. The oldest tree is the tree such that no + * other tree in the array is a descendent of that tree. + * Note that this counter-intuitive concept of oldest is based on the + * ElementTree orientation such that the complete tree is always the + * newest tree. + */ + public static int findOldest(ElementTree[] trees) { + + /* first put all the trees in a hashtable */ + HashMap candidates = new HashMap<>((int) (trees.length * 1.5 + 1)); + for (int i = 0; i < trees.length; i++) { + candidates.put(trees[i], trees[i]); + } + + /* keep removing parents until only one tree remains */ + ElementTree oldestSoFar = null; + while (candidates.size() > 0) { + /* get a new candidate */ + ElementTree current = candidates.values().iterator().next(); + + /* remove this candidate from the table */ + candidates.remove(current); + + /* remove all of this element's parents from the list of candidates*/ + ElementTree parent = current.getParent(); + + /* walk up chain until we hit the root or a tree we have already tested */ + while (parent != null && parent != oldestSoFar) { + candidates.remove(parent); + parent = parent.getParent(); + } + + /* the current candidate is the oldest tree seen so far */ + oldestSoFar = current; + + /* if the table is now empty, we have a winner */ + } + Assert.isNotNull(oldestSoFar); + + /* return the appropriate index */ + for (int i = 0; i < trees.length; i++) { + if (trees[i] == oldestSoFar) { + return i; + } + } + Assert.isTrue(false, "Should not get here"); //$NON-NLS-1$ + return -1; + } + + /** + * Returns the number of children of the element + * specified by the given path. + * The given element must be present in this tree. + */ + public synchronized int getChildCount(IPath key) { + Assert.isNotNull(key); + return getChildIDs(key).length; + } + + /** + * Returns the IDs of the children of the specified element. + * If the specified element is null, returns the root element path. + */ + protected IPath[] getChildIDs(IPath key) { + ChildIDsCache cache = childIDsCache; // Grab it in case it's replaced concurrently. + if (cache != null && cache.path == key) { + return cache.childPaths; + } + try { + if (key == null) + return new IPath[] {tree.rootKey()}; + IPath[] children = tree.getChildren(key); + childIDsCache = new ChildIDsCache(key, children); // Cache the result + return children; + } catch (ObjectNotFoundException e) { + elementNotFound(key); + return null; // can't get here + } + } + + /** + * Returns the paths of the children of the element + * specified by the given path. + * The given element must be present in this tree. + */ + public synchronized IPath[] getChildren(IPath key) { + Assert.isNotNull(key); + return getChildIDs(key); + } + + /** + * Returns the internal data tree. + */ + public DeltaDataTree getDataTree() { + return tree; + } + + /** + * Returns the element data for the given element identifier. + * The given element must be present in this tree. + */ + public synchronized Object getElementData(IPath key) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return null; + DataTreeLookup lookup = lookupCache; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) + lookupCache = lookup = tree.lookup(key); + if (lookup.isPresent) + return lookup.data; + elementNotFound(key); + return null; // can't get here + } + + /** + * Returns the element data for the given element identifier. + * The given element must be present in this tree. + */ + public synchronized Object getElementDataIgnoreCase(IPath key) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return null; + DataTreeLookup lookup = lookupCacheIgnoreCase; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) + lookupCacheIgnoreCase = lookup = tree.lookupIgnoreCase(key); + if (lookup.isPresent) + return lookup.data; + elementNotFound(key); + return null; // can't get here + } + + /** + * Returns the names of the children of the specified element. + * The specified element must exist in the tree. + * If the specified element is null, returns the root element path. + */ + public synchronized String[] getNamesOfChildren(IPath key) { + try { + if (key == null) + return new String[] {""}; //$NON-NLS-1$ + return tree.getNamesOfChildren(key); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + return null; // can't get here + } + } + + /** + * Returns the parent tree, or null if there is no parent. + */ + public ElementTree getParent() { + DeltaDataTree parentTree = tree.getParent(); + if (parentTree == null) { + return null; + } + // The parent ElementTree is stored as the node data of the parent DeltaDataTree, + // to simplify canonicalization in the presence of rerooting. + return (ElementTree) parentTree.getData(tree.rootKey()); + } + + /** + * Returns the root node of this tree. + */ + public IPath getRoot() { + return getChildIDs(null)[0]; + } + + /** + * Returns the subtree rooted at the given key. In the resulting tree, + * the implicit root node (designated by Path.ROOT), has a single child, + * which is the node specified by the given key in this tree. + * + * The subtree must be present in this tree. + * + * @see #createSubtree(IPath, ElementTree) + */ + public ElementTree getSubtree(IPath key) { + /* the subtree of the root of this tree is just this tree */ + if (key.isRoot()) { + return this; + } + try { + DataTreeNode elementNode = (DataTreeNode) tree.copyCompleteSubtree(key); + return new ElementTree(elementNode); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + return null; + } + } + + /** + * Returns the user data associated with this tree. + */ + public IElementTreeData getTreeData() { + return userData; + } + + /** + * Returns true if there have been changes in the tree between the two + * given layers. The two must be related and new must be newer than old. + * That is, new must be an ancestor of old. + */ + public static boolean hasChanges(ElementTree newLayer, ElementTree oldLayer, IElementComparator comparator, boolean inclusive) { + // if any of the layers are null, assume that things have changed + if (newLayer == null || oldLayer == null) + return true; + if (newLayer == oldLayer) + return false; + //if the tree data has changed, then the tree has changed + if (comparator.compare(newLayer.getTreeData(), oldLayer.getTreeData()) != IElementComparator.K_NO_CHANGE) + return true; + + // The tree structure has the top layer(s) (i.e., tree) parentage pointing down to a complete + // layer whose parent is null. The bottom layers (i.e., operationTree) point up to the + // common complete layer whose parent is null. The complete layer moves up as + // changes happen. To see if any changes have happened, we should consider only + // layers whose parent is not null. That is, skip the complete layer as it will clearly not be + // empty. + + // look down from the current layer (always inclusive) if the top layer is mutable + ElementTree stopLayer = null; + if (newLayer.isImmutable()) + // if the newLayer is immutable, the tree structure all points up so ensure that + // when searching up, we stop at newLayer (inclusive) + stopLayer = newLayer.getParent(); + else { + ElementTree layer = newLayer; + while (layer != null && layer.getParent() != null) { + if (!layer.getDataTree().isEmptyDelta()) + return true; + layer = layer.getParent(); + } + } + + // look up from the layer at which we started to null or newLayer's parent (variably inclusive) + // depending on whether newLayer is mutable. + ElementTree layer = inclusive ? oldLayer : oldLayer.getParent(); + while (layer != null && layer.getParent() != stopLayer) { + if (!layer.getDataTree().isEmptyDelta()) + return true; + layer = layer.getParent(); + } + // didn't find anything that changed + return false; + } + + /** + * Makes this tree immutable (read-only); ignored if it is already + * immutable. + */ + public synchronized void immutable() { + if (!tree.isImmutable()) { + tree.immutable(); + /* need to clear the lookup cache since it reports whether results were found + in the topmost delta, and the order of deltas is changing */ + lookupCache = lookupCacheIgnoreCase = null; + /* reroot the delta chain at this tree */ + tree.reroot(); + } + } + + /** + * Returns true if this element tree includes an element with the given + * key, false otherwise. + */ + public synchronized boolean includes(IPath key) { + DataTreeLookup lookup = lookupCache; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) { + lookupCache = lookup = tree.lookup(key); + } + return lookup.isPresent; + } + + /** + * Returns true if this element tree includes an element with the given + * key, ignoring the case of the key, and false otherwise. + */ + public boolean includesIgnoreCase(IPath key) { + DataTreeLookup lookup = lookupCacheIgnoreCase; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) { + lookupCacheIgnoreCase = lookup = tree.lookupIgnoreCase(key); + } + return lookup.isPresent; + } + + protected void initialize(DataTreeNode rootNode) { + /* create the implicit root node */ + initialize(new DeltaDataTree(new DataTreeNode(null, null, new AbstractDataTreeNode[] {rootNode}))); + } + + protected void initialize(DeltaDataTree newTree) { + // Keep this element tree as the data of the root node. + // Useful for canonical results for ElementTree.getParent(). + // see getParent(). + treeStamp = treeCounter++; + newTree.setData(newTree.rootKey(), this); + this.tree = newTree; + } + + /** + * Returns whether this tree is immutable. + */ + public boolean isImmutable() { + return tree.isImmutable(); + } + + /** + * Merges a chain of deltas for a certain subtree to this tree. + * If this tree has any data in the specified subtree, it will + * be overwritten. The receiver tree must be open, and it will + * be made immutable during the merge operation. The trees in the + * provided array will be replaced by new trees that have been + * merged into the receiver's delta chain. + * + * @param path The path of the subtree chain to merge + * @param trees The chain of trees to merge. The trees can be + * in any order, but they must all form a simple ancestral chain. + * @return A new open tree with the delta chain merged in. + */ + public ElementTree mergeDeltaChain(IPath path, ElementTree[] trees) { + if (path == null || trees == null) { + throw new IllegalArgumentException(NLS.bind(Messages.watson_nullArg, "ElementTree.mergeDeltaChain")); //$NON-NLS-1$ + } + + /* The tree has to be open */ + if (isImmutable()) { + throw new IllegalArgumentException(Messages.watson_immutable); + } + ElementTree current = this; + if (trees.length > 0) { + /* find the oldest tree to be merged */ + ElementTree toMerge = trees[findOldest(trees)]; + + /* merge the trees from oldest to newest */ + while (toMerge != null) { + if (path.isRoot()) { + //copy all the children + IPath[] children = toMerge.getChildren(Path.ROOT); + for (int i = 0; i < children.length; i++) { + current.createSubtree(children[i], toMerge.getSubtree(children[i])); + } + } else { + //just copy the specified node + current.createSubtree(path, toMerge.getSubtree(path)); + } + current.immutable(); + + /* replace the tree in the array */ + /* we have to go through all trees because there may be duplicates */ + for (int i = 0; i < trees.length; i++) { + if (trees[i] == toMerge) { + trees[i] = current; + } + } + current = current.newEmptyDelta(); + toMerge = toMerge.getParent(); + } + } + return current; + } + + /** + * Creates a new element tree which is represented as a delta on this one. + * Initially they have the same content. Subsequent changes to the new + * tree will not affect this one. + */ + public synchronized ElementTree newEmptyDelta() { + // Don't want old trees hanging onto cached infos. + lookupCache = lookupCacheIgnoreCase = null; + return new ElementTree(this); + } + + /** + * Returns a mutable copy of the element data for the given path. + * This copy will be held onto in the most recent delta. + * ElementTree data MUST implement the IElementTreeData interface + * for this method to work. If the data does not define that interface + * this method will fail. + */ + public synchronized Object openElementData(IPath key) { + Assert.isTrue(!isImmutable()); + + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return null; + DataTreeLookup lookup = lookupCache; // Grab it in case it's replaced concurrently. + if (lookup == null || lookup.key != key) { + lookupCache = lookup = tree.lookup(key); + } + if (lookup.isPresent) { + if (lookup.foundInFirstDelta) + return lookup.data; + /** + * The node has no data in the most recent delta. + * Pull it up to the present delta by setting its data with a clone. + */ + IElementTreeData oldData = (IElementTreeData) lookup.data; + if (oldData != null) { + try { + Object newData = oldData.clone(); + tree.setData(key, newData); + lookupCache = lookupCacheIgnoreCase = null; + return newData; + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + } else { + elementNotFound(key); + } + return null; + } + + /** + * Sets the element for the given element identifier. + * The given element must be present in this tree. + * @param key element identifier + * @param data element info, or null + */ + public synchronized void setElementData(IPath key, Object data) { + /* don't allow modification of the implicit root */ + if (key.isRoot()) + return; + + Assert.isNotNull(key); + // Clear the lookup cache, in case the element being modified is the same + // as for the last lookup. + lookupCache = lookupCacheIgnoreCase = null; + try { + tree.setData(key, data); + } catch (ObjectNotFoundException e) { + elementNotFound(key); + } + } + + /** + * Sets the user data associated with this tree. + */ + public void setTreeData(IElementTreeData data) { + userData = data; + } + + /* (non-Javadoc) + * Method declared on IStringPoolParticipant + */ + public void shareStrings(StringPool set) { + tree.storeStrings(set); + } + + /** + * Returns a string representation of this element tree's + * structure suitable for debug purposes. + */ + public String toDebugString() { + final StringBuilder buffer = new StringBuilder("\n"); //$NON-NLS-1$ + IElementContentVisitor visitor = new IElementContentVisitor() { + @Override + public boolean visitElement(ElementTree aTree, IPathRequestor elementID, Object elementContents) { + buffer.append(elementID.requestPath() + " " + elementContents + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + return true; + } + }; + new ElementTreeIterator(this, Path.ROOT).iterate(visitor); + return buffer.toString(); + } + + @Override + public String toString() { + return "ElementTree(" + treeStamp + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java new file mode 100644 index 0000000000..113849dbd7 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * 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.watson; + +import org.eclipse.core.internal.dtree.AbstractDataTreeNode; +import org.eclipse.core.internal.dtree.DataTreeNode; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; + +/** + * A class for performing operations on each element in an element tree. + * For example, this can be used to print the contents of a tree. + *

                  + * When creating an ElementTree iterator, an element tree and root path must be + * supplied. When the iterate() method is called, a visitor object + * must be provided. The visitor is called once for each node of the tree. For + * each node, the visitor is passed the entire tree, the object in the tree at + * that node, and a callback for requesting the full path of that node. + *

                  + * Example: +

                  + // printing a crude representation of the poster child
                  + IElementContentVisitor visitor=
                  +     new IElementContentVisitor() {
                  +   public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
                  +     System.out.println(requestor.requestPath() + " -> " + elementContents);
                  +     return true;
                  +   }
                  + });
                  + ElementTreeIterator iterator = new ElementTreeIterator(tree, Path.ROOT);
                  + iterator.iterate(visitor);
                  + 
                  + */ +public class ElementTreeIterator implements IPathRequestor { + //for path requestor + private String[] segments = new String[10]; + private int nextFreeSegment; + + /* the tree being visited */ + private ElementTree tree; + + /* the root of the subtree to visit */ + private IPath path; + + /* the immutable data tree being visited */ + private DataTreeNode treeRoot; + + /** + * Creates a new element tree iterator for visiting the given tree starting + * at the given path. + */ + public ElementTreeIterator(ElementTree tree, IPath path) { + this.tree = tree; + this.path = path; + //treeRoot can be null if deleted concurrently + //must copy the tree while owning the tree's monitor to prevent concurrent deletion while creating visitor's copy + synchronized (tree) { + treeRoot = (DataTreeNode) tree.getDataTree().safeCopyCompleteSubtree(path); + } + } + + /** + * Iterates through the given element tree and visit each element (node) + * passing in the element's ID and element object. + */ + private void doIteration(DataTreeNode node, IElementContentVisitor visitor) { + //push the name of this node to the requestor stack + if (nextFreeSegment >= segments.length) { + grow(); + } + segments[nextFreeSegment++] = node.getName(); + + //do the visit + if (visitor.visitElement(tree, this, node.getData())) { + //recurse + AbstractDataTreeNode[] children = node.getChildren(); + int len = children.length; + for (int i = 0; i < len; i++) { + doIteration((DataTreeNode) children[i], visitor); + } + } + + //pop the segment from the requestor stack + nextFreeSegment--; + if (nextFreeSegment < 0) + nextFreeSegment = 0; + } + + /** + * Method grow. + */ + private void grow() { + //grow the segments array + int oldLen = segments.length; + String[] newPaths = new String[oldLen * 2]; + System.arraycopy(segments, 0, newPaths, 0, oldLen); + segments = newPaths; + } + + /** + * Iterates through this iterator's tree and visits each element in the + * subtree rooted at the given path. The visitor is passed each element's + * data and a request callback for obtaining the path. + */ + public void iterate(IElementContentVisitor visitor) { + if (path.isRoot()) { + //special visit for root element to use special treeData + if (visitor.visitElement(tree, this, tree.getTreeData())) { + if (treeRoot == null) + return; + AbstractDataTreeNode[] children = treeRoot.getChildren(); + int len = children.length; + for (int i = 0; i < len; i++) { + doIteration((DataTreeNode) children[i], visitor); + } + } + } else { + if (treeRoot == null) + return; + push(path, path.segmentCount() - 1); + doIteration(treeRoot, visitor); + } + } + + /** + * Push the first "toPush" segments of this path. + */ + private void push(IPath pathToPush, int toPush) { + if (toPush <= 0) + return; + for (int i = 0; i < toPush; i++) { + if (nextFreeSegment >= segments.length) { + grow(); + } + segments[nextFreeSegment++] = pathToPush.segment(i); + } + } + + @Override + public String requestName() { + if (nextFreeSegment == 0) + return ""; //$NON-NLS-1$ + return segments[nextFreeSegment - 1]; + } + + @Override + public IPath requestPath() { + if (nextFreeSegment == 0) + return Path.ROOT; + int length = nextFreeSegment; + for (int i = 0; i < nextFreeSegment; i++) { + length += segments[i].length(); + } + StringBuilder pathBuf = new StringBuilder(length); + for (int i = 0; i < nextFreeSegment; i++) { + pathBuf.append('/'); + pathBuf.append(segments[i]); + } + return new Path(null, pathBuf.toString()); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.java new file mode 100644 index 0000000000..0947aac02c --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.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 + * Francis Lynch (Wind River) - [305718] Allow reading snapshot into renamed project + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +import java.io.*; +import org.eclipse.core.internal.dtree.DataTreeReader; +import org.eclipse.core.internal.dtree.IDataFlattener; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.runtime.*; + +/** ElementTreeReader is the standard implementation + * of an element tree serialization reader. + * + *

                  Subclasses of this reader read can handle current and various + * known old formats of a saved element tree, and dispatch internally + * to an appropriate reader. + * + *

                  The reader has an IElementInfoFactory, + * which it consults for the schema and for creating + * and reading element infos. + * + *

                  Element tree readers are thread-safe; several + * threads may share a single reader provided, of course, + * that the IElementInfoFactory is thread-safe. + */ +public class ElementTreeReader { + /** The element info factory. + */ + protected IElementInfoFlattener elementInfoFlattener; + + /** + * For reading and writing delta trees + */ + protected DataTreeReader dataTreeReader; + + /** + * Constructs a new element tree reader that works for + * the given element info flattener. + */ + public ElementTreeReader(final IElementInfoFlattener factory) { + Assert.isNotNull(factory); + elementInfoFlattener = factory; + + /* wrap the IElementInfoFlattener in an IDataFlattener */ + IDataFlattener f = new IDataFlattener() { + @Override + public void writeData(IPath path, Object data, DataOutput output) { + //not needed + } + + @Override + public Object readData(IPath path, DataInput input) throws IOException { + //never read the root node of an ElementTree + //this node is reserved for the parent backpointer + if (!Path.ROOT.equals(path)) + return factory.readElement(path, input); + return null; + } + }; + dataTreeReader = new DataTreeReader(f); + } + + /** + * Returns the appropriate reader for the given version. + */ + public ElementTreeReader getReader(int formatVersion) throws IOException { + if (formatVersion == 1) + return new ElementTreeReaderImpl_1(elementInfoFlattener); + throw new IOException(Messages.watson_unknown); + } + + /** + * Reads an element tree delta from the input stream, and + * reconstructs it as a delta on the given tree. + */ + public ElementTree readDelta(ElementTree completeTree, DataInput input) throws IOException { + /* Dispatch to the appropriate reader. */ + ElementTreeReader realReader = getReader(readNumber(input)); + return realReader.readDelta(completeTree, input); + } + + /** + * Reads a chain of ElementTrees from the given input stream. + * @return A chain of ElementTrees, where the first tree in the list is + * complete, and all other trees are deltas on the previous tree in the list. + */ + public ElementTree[] readDeltaChain(DataInput input) throws IOException { + return readDeltaChain(input, ""); //$NON-NLS-1$ + } + + /** + * Reads a chain of ElementTrees from the given input stream. + * @param input the input stream to read from. + * @param newProjectName a new name to use for the root node of the + * tree being read, or the empty String ("") to read the tree + * from the given input unchanged. + * @return A chain of ElementTrees, where the first tree in the list is + * complete, and all other trees are deltas on the previous tree in the list. + s */ + public ElementTree[] readDeltaChain(DataInput input, String newProjectName) throws IOException { + /* Dispatch to the appropriate reader. */ + ElementTreeReader realReader = getReader(readNumber(input)); + return realReader.readDeltaChain(input, newProjectName); + } + + /** + * 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 static int readNumber(DataInput input) 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 an element tree from the input stream and returns it. + * This method actually just dispatches to the appropriate reader + * depending on the stream version id. + */ + public ElementTree readTree(DataInput input) throws IOException { + return readTree(input, ""); //$NON-NLS-1$ + } + + /** + * Reads an element tree from the input stream and returns it. + * This method actually just dispatches to the appropriate reader + * depending on the stream version id. + * @param input the input stream to read from. + * @param newProjectName a new name to use for the root node of the + * tree being read, or the empty String ("") to read the tree + * from the given input unchanged. + * @return the requested ElementTree. + */ + public ElementTree readTree(DataInput input, String newProjectName) throws IOException { + /* Dispatch to the appropriate reader. */ + ElementTreeReader realReader = getReader(readNumber(input)); + return realReader.readTree(input, newProjectName); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java new file mode 100644 index 0000000000..a8bead1266 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * 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) - [305718] Allow reading snapshot into renamed project + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +import java.io.DataInput; +import java.io.IOException; +import org.eclipse.core.internal.dtree.DeltaDataTree; + +/** ElementTreeReader_1 is an implementation + * of the ElementTreeReader for format version 1. + * + *

                  Instances of this reader read only format 1 + * of a saved element tree (they do not deal with + * compatibility issues). + * + * @see ElementTreeReader + */ +/* package */class ElementTreeReaderImpl_1 extends ElementTreeReader { + + /** + * Constructs a new element tree reader that works for + * the given element info factory. + */ + ElementTreeReaderImpl_1(IElementInfoFlattener factory) { + super(factory); + } + + /** + * Reads an element tree delta from the input stream, and + * reconstructs it as a delta on the given tree. + */ + @Override + public ElementTree readDelta(ElementTree parentTree, DataInput input) throws IOException { + DeltaDataTree complete = parentTree.getDataTree(); + DeltaDataTree delta = dataTreeReader.readTree(complete, input, ""); //$NON-NLS-1$ + + //if the delta is empty, just return the parent + if (delta.isEmptyDelta()) + return parentTree; + + ElementTree tree = new ElementTree(delta); + + //copy the user data forward + IElementTreeData data = parentTree.getTreeData(); + if (data != null) { + tree.setTreeData((IElementTreeData) data.clone()); + } + + //make the underlying data tree immutable + //can't call immutable() on the ElementTree because + //this would attempt to reroot. + delta.immutable(); + return tree; + } + + @Override + public ElementTree[] readDeltaChain(DataInput input, String newProjectName) throws IOException { + /* read the number of trees */ + int treeCount = readNumber(input); + ElementTree[] results = new ElementTree[treeCount]; + + if (treeCount <= 0) { + return results; + } + + /* read the sort order */ + int[] order = new int[treeCount]; + for (int i = 0; i < treeCount; i++) { + order[i] = readNumber(input); + } + + /* read the complete tree */ + results[order[0]] = super.readTree(input, newProjectName); + + /* reconstitute each of the remaining trees from their written deltas */ + for (int i = 1; i < treeCount; i++) { + results[order[i]] = super.readDelta(results[order[i - 1]], input); + } + + return results; + } + + @Override + public ElementTree readTree(DataInput input, String newProjectName) throws IOException { + + /* The format version number has already been consumed + * by ElementTreeReader#readFrom. + */ + ElementTree result = new ElementTree(dataTreeReader.readTree(null, input, newProjectName)); + return result; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java new file mode 100644 index 0000000000..a9352f27a1 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java @@ -0,0 +1,242 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + * Mickael Istria (Red Hat Inc.) - Bug 488937 + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +import java.io.*; +import java.util.*; +import org.eclipse.core.internal.dtree.*; +import org.eclipse.core.runtime.*; + +/** ElementTreeWriter flattens an ElementTree + * onto a data output stream. + * + *

                  This writer generates the most up-to-date format + * of a saved element tree (cf. readers, which must usually also + * deal with backward compatibility issues). The flattened + * representation always includes a format version number. + * + *

                  The writer has an IElementInfoFactory, + * which it consults for writing element infos. + * + *

                  Element tree writers are thread-safe; several + * threads may share a single writer. + * + */ +public class ElementTreeWriter { + /** + * The current format version number. + */ + public static final int CURRENT_FORMAT = 1; + + /** + * Constant representing infinite depth + */ + public static final int D_INFINITE = DataTreeWriter.D_INFINITE; + + /** + * For writing DeltaDataTrees + */ + protected DataTreeWriter dataTreeWriter; + + /** + * Constructs a new element tree writer that works for + * the given element info flattener. + */ + public ElementTreeWriter(final IElementInfoFlattener flattener) { + + /* wrap the IElementInfoFlattener in an IDataFlattener */ + IDataFlattener f = new IDataFlattener() { + @Override + public void writeData(IPath path, Object data, DataOutput output) throws IOException { + // never write the root node of an ElementTree + //because it contains the parent backpointer. + if (!Path.ROOT.equals(path)) { + flattener.writeElement(path, data, output); + } + } + + @Override + public Object readData(IPath path, DataInput input) { + return null; + } + }; + dataTreeWriter = new DataTreeWriter(f); + } + + /** + * Sorts the given array of trees so that the following rules are true: + * - The first tree has no parent + * - No tree has an ancestor with a greater index in the array. + * If there are no missing parents in the given trees array, this means + * that in the resulting array, the i'th tree's parent will be tree i-1. + * The input tree array may contain duplicate trees. + * The sort order is written to the given output stream. + */ + protected ElementTree[] sortTrees(ElementTree[] trees, DataOutput output) throws IOException { + + /* the sorted list */ + int numTrees = trees.length; + ElementTree[] sorted = new ElementTree[numTrees]; + int[] order = new int[numTrees]; + + /* first build a table of ElementTree -> Vector of Integers(indices in trees array) */ + HashMap> table = new HashMap<>(numTrees * 2 + 1); + for (int i = 0; i < trees.length; i++) { + List indices = table.get(trees[i]); + if (indices == null) { + indices = new ArrayList<>(); + table.put(trees[i], indices); + } + indices.add(i); + } + + /* find the oldest tree (a descendent of all other trees) */ + ElementTree oldest = trees[ElementTree.findOldest(trees)]; + + /** + * Walk through the chain of trees from oldest to newest, + * adding them to the sorted list as we go. + */ + int i = numTrees - 1; + while (i >= 0) { + /* add all instances of the current oldest tree to the sorted list */ + List indices = table.remove(oldest); + for (Enumeration e = Collections.enumeration(indices); e.hasMoreElements();) { + Integer next = e.nextElement(); + sorted[i] = oldest; + order[i] = next.intValue(); + i--; + } + if (i >= 0) { + /* find the next tree in the list */ + ElementTree parent = oldest.getParent(); + while (table.get(parent) == null) { + parent = parent.getParent(); + } + oldest = parent; + } + } + + /* write the order array */ + for (i = 0; i < numTrees; i++) { + writeNumber(order[i], output); + } + return sorted; + } + + /** + * Writes the delta describing the changes that have to be made + * to newerTree to obtain olderTree. + * + * @param path The path of the subtree to write. All nodes on the path above + * the subtree are represented as empty nodes. + * @param depth The depth of the subtree to write. A depth of zero writes a + * single node, and a depth of D_INFINITE writes the whole subtree. + * @param output The stream to write the subtree to. + */ + public void writeDelta(ElementTree olderTree, ElementTree newerTree, IPath path, int depth, final DataOutput output, IElementComparator comparator) throws IOException { + + /* write the version number */ + writeNumber(CURRENT_FORMAT, output); + + /** + * Note that in current ElementTree usage, the newest + * tree is the complete tree, and older trees are just + * deltas on the new tree. + */ + DeltaDataTree completeTree = newerTree.getDataTree(); + DeltaDataTree derivedTree = olderTree.getDataTree(); + DeltaDataTree deltaToWrite = null; + + deltaToWrite = completeTree.forwardDeltaWith(derivedTree, comparator); + + Assert.isTrue(deltaToWrite.isImmutable()); + dataTreeWriter.writeTree(deltaToWrite, path, depth, output); + } + + /** + * Writes an array of ElementTrees to the given output stream. + * @param trees A chain of ElementTrees, where on tree in the list is + * complete, and all other trees are deltas on the previous tree in the list. + * @param path The path of the subtree to write. All nodes on the path above + * the subtree are represented as empty nodes. + * @param depth The depth of the subtree to write. A depth of zero writes a + * single node, and a depth of D_INFINITE writes the whole subtree. + * @param output The stream to write the subtree to. + + */ + public void writeDeltaChain(ElementTree[] trees, IPath path, int depth, DataOutput output, IElementComparator comparator) throws IOException { + /* Write the format version number */ + writeNumber(CURRENT_FORMAT, output); + + /* Write the number of trees */ + int treeCount = trees.length; + writeNumber(treeCount, output); + + if (treeCount <= 0) { + return; + } + + /** + * Sort the trees in ancestral order, + * which writes the tree order to the output + */ + ElementTree[] sortedTrees = sortTrees(trees, output); + + /* Write the complete tree */ + writeTree(sortedTrees[0], path, depth, output); + + /* Write the deltas for each of the remaining trees */ + for (int i = 1; i < treeCount; i++) { + writeDelta(sortedTrees[i], sortedTrees[i - 1], path, depth, output, comparator); + } + } + + /** + * 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, DataOutput output) throws IOException { + if (number >= 0 && number < 0xff) { + output.writeByte(number); + } else { + output.writeByte(0xff); + output.writeInt(number); + } + } + + /** + * Writes all or some of an element tree to an output stream. + * This always writes the most current version of the element tree + * file format, whereas the reader supports multiple versions. + * + * @param tree The tree to write + * @param path The path of the subtree to write. All nodes on the path above + * the subtree are represented as empty nodes. + * @param depth The depth of the subtree to write. A depth of zero writes a + * single node, and a depth of D_INFINITE writes the whole subtree. + * @param output The stream to write the subtree to. + */ + public void writeTree(ElementTree tree, IPath path, int depth, final DataOutput output) throws IOException { + + /* Write the format version number. */ + writeNumber(CURRENT_FORMAT, output); + + /* This actually just copies the root node, which is what we want */ + DeltaDataTree subtree = new DeltaDataTree(tree.getDataTree().copyCompleteSubtree(Path.ROOT)); + + dataTreeWriter.writeTree(subtree, path, depth, output); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java new file mode 100644 index 0000000000..b3bef46bd0 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * 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.watson; + +import org.eclipse.core.internal.dtree.IComparator; + +/** + * This interface allows clients of the element tree to specify + * how element infos are compared, and thus how element tree deltas + * are created. + */ +public interface IElementComparator extends IComparator { + /** + * The kinds of changes + */ + public int K_NO_CHANGE = 0; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.java new file mode 100644 index 0000000000..4b14f2f53a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.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.watson; + +/** + * An interface for objects which can visit an element of + * an element tree and access that element's node info. + * @see ElementTreeIterator + */ +public interface IElementContentVisitor { + /** Visits a node (element). + *

                  Note that elementContents is equal totree. + * getElement(elementPath) but takes no time. + * @param tree the element tree being visited + * @param elementContents the object at the node being visited on this call + * @param requestor callback object for requesting the path of the object being + * visited. + * @return true if this element's children should be visited, and false + * otherwise. + */ + public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java new file mode 100644 index 0000000000..dd4f03b34a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.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.watson; + +import java.io.*; +import org.eclipse.core.runtime.IPath; + +/** + * The IElementInfoFlattener interface supports + * reading and writing element info objects. + */ +public interface IElementInfoFlattener { + /** + * Reads an element info from the given input stream. + * @param elementPath 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 elementPath, + * which may be null. + */ + public Object readElement(IPath elementPath, DataInput input) throws IOException; + + /** + * Writes the given element to the output stream. + *

                  N.B. The bytes written must be sufficient for the + * purposes of reading the object back in. + * @param elementPath the element's path in the tree + * @param element the object associated with the given path, + * which may be null. + */ + public void writeElement(IPath elementPath, Object element, DataOutput output) throws IOException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java new file mode 100644 index 0000000000..299f95661c --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java @@ -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 + *******************************************************************************/ +package org.eclipse.core.internal.watson; + +/** + * User data that can be attached to the element tree itself. + */ +public interface IElementTreeData extends Cloneable { + /** + * ElementTreeData must define a publicly accessible clone method. + * This method can simply invoke Object's clone method. + */ + public Object clone(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.java new file mode 100644 index 0000000000..0172182241 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.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.watson; + +import org.eclipse.core.runtime.IPath; + +/** + * Callback interface so visitors can request the path of the object they + * are visiting. This avoids creating paths when they are not needed. + */ +public interface IPathRequestor { + public IPath requestPath(); + + public String requestName(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java new file mode 100644 index 0000000000..706bfa6ae7 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/FileInfoMatcherDescription.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2009, 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; + +/** + * A description of a file info matcher. + * @since 3.6 + */ +public final class FileInfoMatcherDescription { + + private String id; + + private Object arguments; + + public FileInfoMatcherDescription(String id, Object arguments) { + super(); + this.id = id; + this.arguments = arguments; + } + + public Object getArguments() { + return arguments; + } + + public String getId() { + return id; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((arguments == null) ? 0 : arguments.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + FileInfoMatcherDescription other = (FileInfoMatcherDescription) obj; + if (arguments == null) { + if (other.arguments != null) + return false; + } else if (!arguments.equals(other.arguments)) + return false; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildConfiguration.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildConfiguration.java new file mode 100644 index 0000000000..d35511a92d --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildConfiguration.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 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.resources; + +import org.eclipse.core.runtime.IAdaptable; + +/** + * Build Configurations provide a mechanism for orthogonal configuration specific + * builds within a single project. The resources plugin maintains build deltas per + * interested builder, per configuration, and allow build configurations to reference + * each other. + *

                  + * All projects have at least one build configuration. By default this + * has name {@link #DEFAULT_CONFIG_NAME}. One configuration in the project is defined + * to be 'active'. The active configuration is built by default. If unset, the + * active configuration defaults to the first configuration in the project. + *

                  + *

                  + * Build configurations are created and set on the project description using: + * {@link IProjectDescription#setBuildConfigs(String[])}. + * Build configurations set on Projects must have unique non-null names. + *

                  + *

                  + * When a project is built, a specific configuration is built. This configuration + * is passed to the builders so they can adapt their behavior + * appropriately. Builders which don't care about configurations may ignore this, + * and work as before. + *

                  + *

                  + * Build configuration can reference other builds configurations. These references are created + * using {@link IWorkspace#newBuildConfig(String, String)}, and set on the referencing project + * with {@link IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[])}. + * A referenced build configuration may have a null configuration name which is resolved to the + * referenced project's current active build configuration at build time. + *

                  + *

                  + * Workspace build will ensure that the projects are built in an appropriate order as defined + * by the reference graph. + *

                  + * + * @see IWorkspace#newBuildConfig(String, String) + * @see IProjectDescription#setActiveBuildConfig(String) + * @see IProjectDescription#setBuildConfigs(String[]) + * @see IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[]) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * @since 3.7 + */ +public interface IBuildConfiguration extends IAdaptable { + + /** + * The Id of the default build configuration + */ + public static final String DEFAULT_CONFIG_NAME = ""; //$NON-NLS-1$ + + /** + * @return the project that the config is for; never null. + */ + public IProject getProject(); + + /** + * Returns the human readable name of this build configuration. If this + * {@link IBuildConfiguration} is set on a Project, this can never be null. + *

                  + * If this IBuildConfiguration is being used as a reference to a build configuration + * in another project, this may be null. Such build configuration references are + * resolved to the current active configuration at build time. + *

                  + * @return the name of the configuration; or null if this is a reference to the active + * configuration + */ + public String getName(); + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java new file mode 100644 index 0000000000..a9836327a4 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IBuildContext.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 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.resources; + +/** + * Stores information about the context in which a builder was called. + * + *

                  + * This can be interrogated by a builder to determine what's been built + * before, and what's being built after it, for this particular build + * invocation. + *

                  + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * + * @since 3.7 + */ +public interface IBuildContext { + + /** + * Gets a array of build configurations that were built before this build configuration, + * as part of the current top-level build invocation. + * + * @return an array of all referenced build configurations that have been built + * in the current build; never null. + */ + public IBuildConfiguration[] getAllReferencedBuildConfigs(); + + /** + * Gets a array of build configurations that will be built after this build configuration, + * as part of the current top-level build invocation. + *

                  + * If the array is empty, this configuration is the last in the build chain. + *

                  + * + * @return an array of all referencing build configurations that will be built + * in the current build; never null. + */ + public IBuildConfiguration[] getAllReferencingBuildConfigs(); + + /** + * Returns the full array of configurations that were requested to be built + * by the API user. These configurations may be anywhere in the build + * order (depending on how the build graph has been flattened). + *

                  + * This array won't include any build configurations being built by virtue + * of being referenced from a requested build configuration. + *

                  + * May return the empty array if this is a top-level workspace build. + * + * @return an array of configurations that were requested to be built. + */ + public IBuildConfiguration[] getRequestedConfigs(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java new file mode 100644 index 0000000000..27670d1ca2 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Map; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A builder command names a builder and supplies a table of + * name-value argument pairs. + *

                  + * Changes to a command will only take effect if the modified command is installed + * into a project description via {@link IProjectDescription#setBuildSpec(ICommand[])}. + *

                  + * + * @see IProjectDescription + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ICommand { + + /** + * Returns a table of the arguments for this command, or null + * if there are no arguments. The argument names and values are both strings. + * + * @return a table of command arguments (key type : String + * value type : String), or null + * @see #setArguments(Map) + */ + public Map getArguments(); + + /** + * Returns the name of the builder to run for this command, or + * null if the name has not been set. + * + * @return the name of the builder, or null if not set + * @see #setBuilderName(String) + */ + public String getBuilderName(); + + /** + * Returns whether this build command responds to the given kind of build. + *

                  + * By default, build commands respond to all kinds of builds. + *

                  + * + * @param kind One of the *_BUILD constants defined + * on IncrementalProjectBuilder + * @return true if this build command responds to the specified + * kind of build, and false otherwise. + * @see #setBuilding(int, boolean) + * @since 3.1 + */ + public boolean isBuilding(int kind); + + /** + * Returns whether this command allows configuring of what kinds of builds + * it responds to. By default, commands are only configurable + * if the corresponding builder defines the {@link #isConfigurable} + * attribute in its builder extension declaration. A command that is not + * configurable will always respond to all kinds of builds. + * + * @return true If this command allows configuration of + * what kinds of builds it responds to, and false otherwise. + * @see #setBuilding(int, boolean) + * @since 3.1 + */ + public boolean isConfigurable(); + + /** + * Sets this command's arguments to be the given table of name-values + * pairs, or to null if there are no arguments. The argument + * names and values are both strings. + *

                  + * Individual builders specify their argument expectations. + *

                  + *

                  + * Note that modifications to the arguments of a command + * being used in a running builder may affect the run of that builder + * but will not affect any subsequent runs. To change a command + * permanently you must install the command into the relevant project + * build spec using {@link IProjectDescription#setBuildSpec(ICommand[])}. + *

                  + * + * @param args a table of command arguments (keys and values must + * both be of type String), or null + * @see #getArguments() + */ + public void setArguments(Map args); + + /** + * Sets the name of the builder to run for this command. + *

                  + * The builder name comes from the extension that plugs in + * to the standard org.eclipse.core.resources.builders + * extension point. + *

                  + * + * @param builderName the name of the builder + * @see #getBuilderName() + */ + public void setBuilderName(String builderName); + + /** + * Specifies whether this build command responds to the provided kind of build. + *

                  + * When a command is configured to not respond to a given kind of build, the + * builder instance will not be called when a build of that kind is initiated. + *

                  + * This method has no effect if this build command does not allow its + * build kinds to be configured. + *

                  + * Note: + *

                    + *
                  • A request for INCREMENTAL_BUILD or AUTO_BUILD will result in the builder being called with the FULL_BUILD + * kind, if there is no previous delta (e.g. after a clean build). + *
                  • + * If INCREMENTAL_BUILD (or AUTO_BUILD) is promoted to FULL_BUILD, the builder will be called, + * if the command responds to INCREMENTAL_BUILD (or AUTO_BUILD). + *
                  • + * If INCREMENTAL_BUILD is promoted to FULL_BUILD, the builder will be called, + * if the command responds to FULL_BUILD. + *
                  • + * If AUTO_BUILD is promoted to FULL_BUILD, the builder will be called, + * only if the command responds to AUTO_BUILD. + *
                  • + *
                  + * + * @param kind One of the *_BUILD constants defined + * on IncrementalProjectBuilder + * @param value true if this build command responds to the + * specified kind of build, and false otherwise. + * @see #isBuilding(int) + * @see #isConfigurable() + * @see IWorkspace#build(int, IProgressMonitor) + * @see IProject#build(int, IProgressMonitor) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#AUTO_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @since 3.1 + */ + public void setBuilding(int kind, boolean value); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java new file mode 100644 index 0000000000..be92d364b5 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java @@ -0,0 +1,562 @@ +/******************************************************************************* + * 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 + * Serge Beauchamp (Freescale Semiconductor) - [252996] add addFilter/removeFilter/getFilters + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.runtime.*; + +/** + * Interface for resources which may contain + * other resources (termed its members). While the + * workspace itself is not considered a container in this sense, the + * workspace root resource is a container. + *

                  + * Containers implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

                  + * + * @see Platform#getAdapterManager() + * @see IProject + * @see IFolder + * @see IWorkspaceRoot + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IContainer extends IResource, IAdaptable { + + /*==================================================================== + * Constants defining which members are wanted: + *====================================================================*/ + + /** + * Member constant (bit mask value 1) indicating that phantom member resources are + * to be included. + * + * @see IResource#isPhantom() + * @since 2.0 + */ + public static final int INCLUDE_PHANTOMS = 1; + + /** + * Member constant (bit mask value 2) indicating that team private members are + * to be included. + * + * @see IResource#isTeamPrivateMember() + * @since 2.0 + */ + public static final int INCLUDE_TEAM_PRIVATE_MEMBERS = 2; + + /** + * Member constant (bit mask value 4) indicating that derived resources + * are to be excluded. + * + * @see IResource#isDerived() + * @since 3.1 + */ + public static final int EXCLUDE_DERIVED = 4; + + /** + * Member constant (bit mask value 8) indicating that hidden resources + * are to be included. + * + * @see IResource#isHidden() + * @since 3.4 + */ + public static final int INCLUDE_HIDDEN = 8; + + /** + * Member constant (bit mask value 16) indicating that a resource + * should not be checked for existence. + * + * @see IResource#accept(IResourceProxyVisitor, int) + * @see IResource#accept(IResourceVisitor, int, int) + * @since 3.8 + */ + public static final int DO_NOT_CHECK_EXISTENCE = 16; + + /** + * Returns whether a resource of some type with the given path + * exists relative to this resource. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators are ignored. + * If the path is empty this container is checked for existence. + * + * @param path the path of the resource + * @return true if a resource of some type with the given path + * exists relative to this resource, and false otherwise + * @see IResource#exists() + */ + public boolean exists(IPath path); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

                  + * Note that no attempt is made to exclude team-private member resources + * as with members. + *

                  + * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource existing at the calculated path in the workspace. + *

                  + * Note that path contains a relative path to the resource + * and all path special characters will be interpreted. Passing an empty string + * will result in returning this {@link IContainer} itself. + *

                  + * + * @param path the relative path to the member resource, must be a valid path or path segment + * @return the member resource, or null if no such + * resource exists + * @see #members() + * @see IPath#isValidPath(String) + * @see IPath#isValidSegment(String) + */ + public IResource findMember(String path); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

                  + * If the includePhantoms argument is false, + * only a member resource with the given path that exists will be returned. + * If the includePhantoms argument is true, + * the method also returns a resource if the workspace is keeping track of a + * phantom with that path. + *

                  + * Note that no attempt is made to exclude team-private member resources + * as with members. + *

                  + * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource (or phantom) existing at the calculated path in the workspace. + *

                  + * Note that path contains a relative path to the resource + * and all path special characters will be interpreted. Passing an empty string + * will result in returning this {@link IContainer} itself. + *

                  + * + * @param path the relative path to the member resource, must be a valid path or path segment + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @return the member resource, or null if no such + * resource exists + * @see #members(boolean) + * @see IResource#isPhantom() + * @see IPath#isValidPath(String) + * @see IPath#isValidSegment(String) + */ + public IResource findMember(String path, boolean includePhantoms); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

                  + * Note that no attempt is made to exclude team-private member resources + * as with members. + *

                  + * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource existing at the calculated path in the workspace. + *

                  + * + * @param path the path of the desired resource + * @return the member resource, or null if no such + * resource exists + */ + public IResource findMember(IPath path); + + /** + * Finds and returns the member resource identified by the given path in + * this container, or null if no such resource exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource. Trailing separators and the path's + * device are ignored. If the path is empty this container is returned. Parent + * references in the supplied path are discarded if they go above the workspace + * root. + *

                  + * If the includePhantoms argument is false, + * only a member resource with the given path that exists will be returned. + * If the includePhantoms argument is true, + * the method also returns a resource if the workspace is keeping track of a + * phantom with that path. + *

                  + * Note that no attempt is made to exclude team-private member resources + * as with members. + *

                  + * N.B. Unlike the methods which traffic strictly in resource + * handles, this method infers the resulting resource's type from the + * resource (or phantom) existing at the calculated path in the workspace. + *

                  + * + * @param path the path of the desired resource + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @return the member resource, or null if no such + * resource exists + * @see #members(boolean) + * @see IResource#isPhantom() + */ + public IResource findMember(IPath path, boolean includePhantoms); + + /** + * Returns the default charset for resources in this container. + *

                  + * This is a convenience method, fully equivalent to: + *

                  +	 *   getDefaultCharset(true);
                  +	 * 
                  + *

                  + * Note that this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

                  + * + * @return the name of the default charset encoding for this container + * @exception CoreException if this method fails + * @see IContainer#getDefaultCharset(boolean) + * @see IFile#getCharset() + * @since 3.0 + */ + public String getDefaultCharset() throws CoreException; + + /** + * Returns the default charset for resources in this container. + *

                  + * If checkImplicit is false, this method + * will return the charset defined by calling #setDefaultCharset, provided this + * container exists, or null otherwise. + *

                  + * If checkImplicit is true, this method uses the following + * algorithm to determine the charset to be returned: + *

                    + *
                  1. the one explicitly set by calling #setDefaultCharset + * (with a non-null argument) on this container, if any, and this container + * exists, or
                  2. + *
                  3. the parent's default charset, if this container has a parent (is not the + * workspace root), or
                  4. + *
                  5. the charset returned by ResourcesPlugin#getEncoding.
                  6. + *
                  + *

                  + * Note that this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

                  + * @return the name of the default charset encoding for this container, + * or null + * @exception CoreException if this method fails + * @see IFile#getCharset() + * @since 3.0 + */ + public String getDefaultCharset(boolean checkImplicit) throws CoreException; + + /** + * Returns a handle to the file identified by the given path in this + * container. + *

                  + * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

                  + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource and is appended + * to this container's full path to form the full path of the resultant resource. + * A trailing separator is ignored. The path of the resulting resource must + * have at least two segments. + *

                  + * + * @param path the path of the member file + * @return the (handle of the) member file + * @see #getFolder(IPath) + */ + public IFile getFile(IPath path); + + /** + * Returns a handle to the folder identified by the given path in this + * container. + *

                  + * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

                  + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this resource and is appended + * to this container's full path to form the full path of the resultant resource. + * A trailing separator is ignored. The path of the resulting resource must + * have at least two segments. + *

                  + * + * @param path the path of the member folder + * @return the (handle of the) member folder + * @see #getFile(IPath) + */ + public IFolder getFolder(IPath path); + + /** + * Returns a list of existing member resources (projects, folders and files) + * in this resource, in no particular order. + *

                  + * This is a convenience method, fully equivalent to members(IResource.NONE). + * Team-private member resources are not included in the result. + *

                  + * Note that the members of a project or folder are the files and folders + * immediately contained within it. The members of the workspace root + * are the projects in the workspace. + *

                  + * + * @return an array of members of this resource + * @exception CoreException if this request fails. Reasons include: + *
                    + *
                  • This resource does not exist.
                  • + *
                  • This resource is a project that is not open.
                  • + *
                  + * @see #findMember(IPath) + * @see IResource#isAccessible() + */ + public IResource[] members() throws CoreException; + + /** + * Returns a list of all member resources (projects, folders and files) + * in this resource, in no particular order. + *

                  + * This is a convenience method, fully equivalent to: + *

                  +	 *   members(includePhantoms ? INCLUDE_PHANTOMS : IResource.NONE);
                  +	 * 
                  + * Team-private member resources are not included in the result. + *

                  + * + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @return an array of members of this resource + * @exception CoreException if this request fails. Reasons include: + *
                    + *
                  • This resource does not exist.
                  • + *
                  • includePhantoms is false and + * this resource does not exist.
                  • + *
                  • includePhantoms is false and + * this resource is a project that is not open.
                  • + *
                  + * @see #members(int) + * @see IResource#exists() + * @see IResource#isPhantom() + */ + public IResource[] members(boolean includePhantoms) throws CoreException; + + /** + * Returns a list of all member resources (projects, folders and files) + * in this resource, in no particular order. + *

                  + * If the INCLUDE_PHANTOMS flag is not specified in the member + * flags (recommended), only member resources that exist will be returned. + * If the INCLUDE_PHANTOMS flag is specified, + * the result will also include any phantom member resources the + * workspace is keeping track of. + *

                  + * If the INCLUDE_TEAM_PRIVATE_MEMBERS flag is specified + * in the member flags, team private members will be included along with + * the others. If the INCLUDE_TEAM_PRIVATE_MEMBERS flag + * is not specified (recommended), the result will omit any team private + * member resources. + *

                  + * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, hidden + * members will be included along with the others. If the {@link #INCLUDE_HIDDEN} flag + * is not specified (recommended), the result will omit any hidden + * member resources. + *

                  + *

                  + * If the EXCLUDE_DERIVED flag is not specified, derived + * resources are included. If the EXCLUDE_DERIVED flag is + * specified in the member flags, derived resources are not included. + *

                  + * + * @param memberFlags bit-wise or of member flag constants + * ({@link #INCLUDE_PHANTOMS}, {@link #INCLUDE_TEAM_PRIVATE_MEMBERS}, + * {@link #INCLUDE_HIDDEN} and {@link #EXCLUDE_DERIVED}) indicating which members are of interest + * @return an array of members of this resource + * @exception CoreException if this request fails. Reasons include: + *
                    + *
                  • This resource does not exist.
                  • + *
                  • the INCLUDE_PHANTOMS flag is not specified and + * this resource does not exist.
                  • + *
                  • the INCLUDE_PHANTOMS flag is not specified and + * this resource is a project that is not open.
                  • + *
                  + * @see IResource#exists() + * @since 2.0 + */ + public IResource[] members(int memberFlags) throws CoreException; + + /** + * Returns a list of recently deleted files inside this container that + * have one or more saved states in the local history. The depth parameter + * determines how deep inside the container to look. This resource may or + * may not exist in the workspace. + *

                  + * When applied to an existing project resource, this method returns recently + * deleted files with saved states in that project. Note that local history is + * maintained with each individual project, and gets discarded when a project + * is deleted from the workspace. If applied to a deleted project, this method + * returns the empty list. + *

                  + * When applied to the workspace root resource (depth infinity), this method + * returns all recently deleted files with saved states in all existing projects. + *

                  + * When applied to a folder (or project) resource (depth one), + * this method returns all recently deleted member files with saved states. + *

                  + * When applied to a folder resource (depth zero), + * this method returns an empty list unless there was a recently deleted file + * with saved states at the same path as the folder. + *

                  + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                  + * + * @param depth depth limit: one of DEPTH_ZERO, DEPTH_ONE + * or DEPTH_INFINITE + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @return an array of recently deleted files + * @exception CoreException if this method fails + * @see IFile#getHistory(IProgressMonitor) + * @since 2.0 + */ + public IFile[] findDeletedMembersWithHistory(int depth, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the default charset for this container. Passing a value of null + * will remove the default charset setting for this resource. + * + * @param charset a charset string, or null + * @exception CoreException if this method fails Reasons include: + *
                    + *
                  • This resource does not exist.
                  • + *
                  • An error happened while persisting this setting.
                  • + *
                  + * @see IContainer#getDefaultCharset() + * @since 3.0 + * @deprecated Replaced by {@link #setDefaultCharset(String, IProgressMonitor)} which + * is a workspace operation and reports changes in resource deltas. + */ + @Deprecated + public void setDefaultCharset(String charset) throws CoreException; + + /** + * Sets the default charset for this container. Passing a value of null + * will remove the default charset setting for this resource. + *

                  + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the encoding of affected resources has been changed. + *

                  + *

                  + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                  + * + * @param charset a charset string, or null + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @exception CoreException if this method fails Reasons include: + *
                    + *
                  • This resource is not accessible.
                  • + *
                  • An error happened while persisting this setting.
                  • + *
                  • Resource changes are disallowed during certain types of resource change + * event notification. See {@link IResourceChangeEvent} for more details.
                  • + *
                  + * @see IContainer#getDefaultCharset() + * @see IResourceRuleFactory#charsetRule(IResource) + * @since 3.0 + */ + public void setDefaultCharset(String charset, IProgressMonitor monitor) throws CoreException; + + /** + * Adds a new filter to this container. Filters restrict the set of files and directories + * in the underlying file system that will be included as members of this container. + *

                  + * The {@link IResource#BACKGROUND_REFRESH} update flag controls when + * changes to the resource hierarchy under this container resulting from the new + * filter take effect. If this flag is specified, the resource hierarchy is updated in a + * separate thread after this method returns. If the flag is not specified, any resource + * changes resulting from the new filter will occur before this method returns. + *

                  + *

                  + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include an indication of any + * resources that have been removed as a result of the new filter. + *

                  + *

                  + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                  + * + * @param type ({@link IResourceFilterDescription#INCLUDE_ONLY} or + * {@link IResourceFilterDescription#EXCLUDE_ALL} and/or {@link IResourceFilterDescription#INHERITABLE}) + * @param matcherDescription the description of the matcher that will determine + * which {@link IFileInfo} instances will be excluded from the resource tree + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#BACKGROUND_REFRESH}) + * @param monitor a progress monitor, or null if progress reporting is not desired + * @return the description of the added filter + * @exception CoreException if this filter could not be added. Reasons include: + *
                    + *
                  • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                  • + *
                  + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #getFilters() + * @see IResourceFilterDescription#delete(int, IProgressMonitor) + * + * @since 3.6 + */ + public IResourceFilterDescription createFilter(int type, FileInfoMatcherDescription matcherDescription, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Retrieve all filters on this container. + * If no filters exist for this resource, an empty array is returned. + * + * @return an array of filters + * @exception CoreException if this resource's filters could not be retrieved. Reasons include: + *
                    + *
                  • This resource is not a folder.
                  • + * + * @see #createFilter(int, FileInfoMatcherDescription, int, IProgressMonitor) + * @see IResourceFilterDescription#delete(int, IProgressMonitor) + * @since 3.6 + */ + public IResourceFilterDescription[] getFilters() throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IDynamicReferenceProvider.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IDynamicReferenceProvider.java new file mode 100644 index 0000000000..7392f8aa4d --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IDynamicReferenceProvider.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 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.resources; + +import java.util.List; +import org.eclipse.core.runtime.CoreException; + +/** + * Implementations of this interface are capable of determining a set + * of projects which a given project depends upon. Unless otherwise stated, + * all arguments and return values are non-null. + * + * @since 3.12 + */ +public interface IDynamicReferenceProvider { + /** + * Returns the set of projects which the given project depends upon. If the return + * value of a previous call to this method ever changes, it will fire an event to + * the listeners. This method my be invoked from any thread and may be invoked + * in parallel by multiple threads. + * + * @param buildConfiguration the build configuration being queried. + * @return the set of projects which the given projects depends upon. + */ + public List getDependentProjects(IBuildConfiguration buildConfiguration) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java new file mode 100644 index 0000000000..f9e7873567 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IEncodedStorage.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * A storage that knows how its contents are encoded. + * + *

                    The IEncodedStorage interface extends IStorage + * in order to provide access to the charset to be used when decoding its + * contents. + *

                    + * Clients may implement this interface. + *

                    + * + * @since 3.0 + */ +public interface IEncodedStorage extends IStorage { + /** + * Returns the name of a charset encoding to be used when decoding this + * storage's contents into characters. Returns null if a proper + * encoding cannot be determined. + *

                    + * Note that this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

                    + * + * @return the name of a charset, or null + * @exception CoreException if an error happens while determining + * the charset. See any refinements for more information. + * @see IStorage#getContents() + */ + public String getCharset() throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java new file mode 100644 index 0000000000..9b968db6a4 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java @@ -0,0 +1,1162 @@ +/******************************************************************************* + * 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 + * Sergey Prigogin (Google) - [462440] IFile#getContents methods should specify the status codes for its exceptions + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.io.InputStream; +import java.io.Reader; +import java.net.URI; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentDescription; +import org.eclipse.core.runtime.content.IContentTypeManager; + +/** + * Files are leaf resources which contain data. + * The contents of a file resource is stored as a file in the local + * file system. + *

                    + * Files, like folders, may exist in the workspace but + * not be local; non-local file resources serve as place-holders for + * files whose content and properties have not yet been fetched from + * a repository. + *

                    + *

                    + * Files implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

                    + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFile extends IResource, IEncodedStorage, IAdaptable { + /** + * Character encoding constant (value 0) which identifies + * files that have an unknown character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UNKNOWN = 0; + /** + * Character encoding constant (value 1) which identifies + * files that are encoded with the US-ASCII character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_US_ASCII = 1; + /** + * Character encoding constant (value 2) which identifies + * files that are encoded with the ISO-8859-1 character encoding scheme, + * also known as ISO-LATIN-1. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_ISO_8859_1 = 2; + /** + * Character encoding constant (value 3) which identifies + * files that are encoded with the UTF-8 character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_8 = 3; + /** + * Character encoding constant (value 4) which identifies + * files that are encoded with the UTF-16BE character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_16BE = 4; + /** + * Character encoding constant (value 5) which identifies + * files that are encoded with the UTF-16LE character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_16LE = 5; + /** + * Character encoding constant (value 6) which identifies + * files that are encoded with the UTF-16 character encoding scheme. + * + * @see IFile#getEncoding() + * @deprecated see getEncoding for details + */ + @Deprecated + public int ENCODING_UTF_16 = 6; + + /** + * Appends the entire contents of the given stream to this file. + *

                    + * This is a convenience method, fully equivalent to: + *

                    +	 *   appendContents(source, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
                    +	 * 
                    + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

                    + *

                    + * This method is long-running; progress and cancelation are provided + * by the given progress monitor. + *

                    + * + * @param source an input stream containing the new contents of the file + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param keepHistory a flag indicating whether or not to store + * the current contents in the local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource does not exist.
                    • + *
                    • The corresponding location in the local file system + * is occupied by a directory.
                    • + *
                    • The workspace is not in sync with the corresponding location + * in the local file system and force is false.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    • The file modification validator disallowed the change.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #appendContents(java.io.InputStream,int,IProgressMonitor) + */ + public void appendContents(InputStream source, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Appends the entire contents of the given stream to this file. + * The stream, which must not be null, will get closed + * whether this method succeeds or fails. + *

                    + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite a corresponding file in the local file system provided + * it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write a corresponding file in the local file system, overwriting any + * existing one if need be. In either case, if this method succeeds, the + * resource will be marked as being local (even if it wasn't before). + *

                    + *

                    + * If this file is non-local then this method will always fail. The only exception + * is when FORCE is specified and the file exists in the local + * file system. In this case the file is made local and the given contents are appended. + *

                    + *

                    + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of this file should be captured in the workspace's local + * history (properties are not recorded in the local history). The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying KEEP_HISTORY + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. This flag is ignored if the file was not previously local. + *

                    + *

                    + * Update flags other than FORCE and KEEP_HISTORY + * are ignored. + *

                    + *

                    + * Prior to modifying the contents of this file, the file modification validator (if provided + * by the VCM plug-in), will be given a chance to perform any last minute preparations. Validation + * is performed by calling IFileModificationValidator.validateSave on this file. + * If the validation fails, then this operation will fail. + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

                    + *

                    + * This method is long-running; progress and cancelation are provided + * by the given progress monitor. + *

                    + * + * @param source an input stream containing the new contents of the file + * @param updateFlags bit-wise or of update flag constants + * (FORCE and KEEP_HISTORY) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource does not exist.
                    • + *
                    • The corresponding location in the local file system + * is occupied by a directory.
                    • + *
                    • The workspace is not in sync with the corresponding location + * in the local file system and FORCE is not specified.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    • The file modification validator disallowed the change.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void appendContents(InputStream source, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + *

                    + * This is a convenience method, fully equivalent to: + *

                    +	 *   create(source, (force ? FORCE : IResource.NONE), monitor);
                    +	 * 
                    + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param source an input stream containing the initial contents of the file, + * or null if the file should be marked as not local + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource already exists in the workspace.
                    • + *
                    • The parent of this resource does not exist.
                    • + *
                    • The parent of this resource is a virtual folder.
                    • + *
                    • The project of this resource is not accessible.
                    • + *
                    • The parent contains a resource of a different type + * at the same path as this resource.
                    • + *
                    • The name of this resource is not valid (according to + * IWorkspace.validateName).
                    • + *
                    • The corresponding location in the local file system is occupied + * by a directory.
                    • + *
                    • The corresponding location in the local file system is occupied + * by a file and force is false.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + */ + public void create(InputStream source, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + * The resource's contents are supplied by the data in the given stream. + * This method closes the stream whether it succeeds or fails. + * If the stream is null then a file is not created in the local + * file system and the created file resource is marked as being non-local. + *

                    + * The {@link IResource#FORCE} update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If {@link IResource#FORCE} is not specified, the method will only attempt + * to write a file in the local file system if it does not already exist. + * This option ensures there is no unintended data loss; it is the recommended + * setting. However, if {@link IResource#FORCE} is specified, this method will + * attempt to write a corresponding file in the local file system, + * overwriting any existing one if need be. + *

                    + *

                    + * The {@link IResource#DERIVED} update flag indicates that this resource + * should immediately be set as a derived resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setDerived(boolean)} + * with a value of true immediately after creating the resource. + *

                    + *

                    + * The {@link IResource#TEAM_PRIVATE} update flag indicates that this resource + * should immediately be set as a team private resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setTeamPrivateMember(boolean)} + * with a value of true immediately after creating the resource. + *

                    + *

                    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

                    + *

                    + * Update flags other than those listed above are ignored. + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param source an input stream containing the initial contents of the file, + * or null if the file should be marked as not local + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#FORCE}, {@link IResource#DERIVED}, and {@link IResource#TEAM_PRIVATE}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource already exists in the workspace.
                    • + *
                    • The parent of this resource does not exist.
                    • + *
                    • The parent of this resource is a virtual folder.
                    • + *
                    • The project of this resource is not accessible.
                    • + *
                    • The parent contains a resource of a different type + * at the same path as this resource.
                    • + *
                    • The name of this resource is not valid (according to + * IWorkspace.validateName).
                    • + *
                    • The corresponding location in the local file system is occupied + * by a directory.
                    • + *
                    • The corresponding location in the local file system is occupied + * by a file and FORCE is not specified.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#createRule(IResource) + * @since 2.0 + */ + public void create(InputStream source, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + * The file's contents will be located in the file specified by the given + * file system path. The given path must be either an absolute file system + * path, or a relative path whose first segment is the name of a workspace path + * variable. + *

                    + * The {@link IResource#ALLOW_MISSING_LOCAL} update flag controls how this + * method deals with cases where the local file system file to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If {@link IResource#ALLOW_MISSING_LOCAL} is specified, the operation will succeed + * even if the local file is missing, or the path is relative to an undefined + * variable. If {@link IResource#ALLOW_MISSING_LOCAL} is not specified, the operation + * will fail in the case where the local file system file does not exist or the + * path is relative to an undefined variable. + *

                    + *

                    + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then the existing linked resource's location is replaced + * by localLocation's value. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

                    + *

                    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

                    + *

                    + * Update flags other than those listed above are ignored. + *

                    + *

                    + * This method synchronizes this resource with the local file system at the given + * location. + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param localLocation a file system path where the file should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE} and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource already exists in the workspace.
                    • + *
                    • The workspace contains a resource of a different type + * at the same path as this resource.
                    • + *
                    • The parent of this resource does not exist.
                    • + *
                    • The parent of this resource is not an open project
                    • + *
                    • The name of this resource is not valid (according to + * IWorkspace.validateName).
                    • + *
                    • The corresponding location in the local file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
                    • + *
                    • The corresponding location in the local file system is occupied + * by a directory (as opposed to a file).
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    • The team provider for the project which contains this folder does not permit + * linked resources.
                    • + *
                    • This folder's project contains a nature which does not permit linked resources.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#HIDDEN + * @since 2.1 + */ + public void createLink(IPath localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new file resource as a member of this handle's parent resource. + * The file's contents will be located in the file specified by the given + * URI. The given URI must be either absolute, or a relative URI whose first path + * segment is the name of a workspace path variable. + *

                    + * The ALLOW_MISSING_LOCAL update flag controls how this + * method deals with cases where the file system file to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If ALLOW_MISSING_LOCAL is specified, the operation will succeed + * even if the local file is missing, or the path is relative to an undefined + * variable. If ALLOW_MISSING_LOCAL is not specified, the operation + * will fail in the case where the file system file does not exist or the + * path is relative to an undefined variable. + *

                    + *

                    + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then any existing resource with the same name is removed + * from the workspace to make way for creation of the link. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

                    + *

                    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

                    + *

                    + * Update flags other than those listed above are ignored. + *

                    + *

                    + * This method synchronizes this resource with the file system at the given + * location. + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the file has been added to its parent. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param location a file system URI where the file should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE} and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource already exists in the workspace.
                    • + *
                    • The workspace contains a resource of a different type + * at the same path as this resource.
                    • + *
                    • The parent of this resource does not exist.
                    • + *
                    • The parent of this resource is not an open project
                    • + *
                    • The name of this resource is not valid (according to + * IWorkspace.validateName).
                    • + *
                    • The corresponding location in the file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
                    • + *
                    • The corresponding location in the file system is occupied + * by a directory (as opposed to a file).
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    • The team provider for the project which contains this folder does not permit + * linked resources.
                    • + *
                    • This folder's project contains a nature which does not permit linked resources.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#HIDDEN + * @since 3.2 + */ + public void createLink(URI location, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this file from the workspace. + *

                    + * This is a convenience method, fully equivalent to: + *

                    +	 *   delete((keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
                    +	 * 
                    + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this folder has been removed from its parent. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource could not be deleted for some reason.
                    • + *
                    • This resource is out of sync with the local file system + * and force is false.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + * @see IResourceRuleFactory#deleteRule(IResource) + */ + public void delete(boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the name of a charset to be used when decoding the contents of this + * file into characters. + *

                    + * This refinement of the corresponding {@link IEncodedStorage} method + * is a convenience method, fully equivalent to: + *

                    +	 *   getCharset(true);
                    +	 * 
                    + *

                    + * Note 1: this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

                    + *

                    + * Note 2: this method returns a cached value for the encoding + * that may be out of date if the file is not synchronized with the local file system + * and the encoding has since changed in the file system. + *

                    + * + * @return the name of a charset + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource could not be read.
                    • + *
                    • This resource is not local.
                    • + *
                    • The corresponding location in the local file system + * is occupied by a directory.
                    • + *
                    + * @see IFile#getCharset(boolean) + * @see IEncodedStorage#getCharset() + * @see IContainer#getDefaultCharset() + * @since 3.0 + */ + @Override + public String getCharset() throws CoreException; + + /** + * Returns the name of a charset to be used when decoding the contents of this + * file into characters. + *

                    + * If checkImplicit is false, this method will return the + * charset defined by calling setCharset, provided this file + * exists, or null otherwise. + *

                    + * If checkImplicit is true, this method uses the following + * algorithm to determine the charset to be returned: + *

                      + *
                    1. the charset defined by calling #setCharset, if any, and this file + * exists, or
                    2. + *
                    3. the charset automatically discovered based on this file's contents, + * if one can be determined, or
                    4. + *
                    5. the default encoding for this file's parent (as defined by + * IContainer#getDefaultCharset).
                    6. + *
                    + *

                    + * Note 1: this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

                    + *

                    + * Note 2: this method returns a cached value for the encoding + * that may be out of date if the file is not synchronized with the local file system + * and the encoding has since changed in the file system. + *

                    + * + * @return the name of a charset, or null + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource could not be read.
                    • + *
                    • This resource is not local.
                    • + *
                    • The corresponding location in the local file system + * is occupied by a directory.
                    • + *
                    + * @see IEncodedStorage#getCharset() + * @see IContainer#getDefaultCharset() + * @since 3.0 + */ + public String getCharset(boolean checkImplicit) throws CoreException; + + /** + * Returns the name of a charset to be used to encode the given contents + * when saving to this file. This file does not have to exist. The character stream is not automatically closed. + *

                    + * This method uses the following algorithm to determine the charset to be returned: + *

                      + *
                    1. if this file exists, the charset returned by IFile#getCharset(false), if one is defined, or
                    2. + *
                    3. the charset automatically discovered based on the file name and the given contents, + * if one can be determined, or
                    4. + *
                    5. the default encoding for the parent resource (as defined by + * IContainer#getDefaultCharset).
                    6. + *
                    + *

                    + * Note: this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * UnsupportedEncodingException where this charset is used. + *

                    + * + * @param reader a character stream containing the contents to be saved into this file + * @return the name of a charset + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • The given character stream could not be read.
                    • + *
                    + * @see #getCharset(boolean) + * @see IContainer#getDefaultCharset() + * @since 3.1 + */ + public String getCharsetFor(Reader reader) throws CoreException; + + /** + * Returns a description for this file's current contents. Returns + * null if a description cannot be obtained. + *

                    + * Calling this method produces a similar effect as calling + * getDescriptionFor(getContents(), getName(), IContentDescription.ALL) + * on IContentTypeManager, but provides better + * opportunities for improved performance. Therefore, when manipulating + * IFiles, clients should call this method instead of + * IContentTypeManager.getDescriptionFor. + *

                    + * + * @return a description for this file's current contents, or + * null + * @exception CoreException if this method fails. The status code associated with exception + * reflects the cause of the failure. Reasons include: + *
                      + *
                    • {@link IResourceStatus#RESOURCE_NOT_FOUND} - This file does not exist. + * Please notice that a successful {@link #exists()} check prior to calling + * {@link #getContentDescription()} does not guarantee the file existence since the file + * may be deleted outside Eclipse at the very last moment.
                    • + *
                    • {@link IResourceStatus#RESOURCE_NOT_LOCAL} - This resource is not local.
                    • + *
                    • {@link IResourceStatus#OUT_OF_SYNC_LOCAL} - The workspace is not in sync with + * the corresponding location in the local file system (and + * {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is disabled).
                    • + *
                    • {@link IResourceStatus#FAILED_DESCRIBING_CONTENTS} - An I/O error occurred while + * reading the file.
                    • + *
                    + * @see IContentDescription + * @see IContentTypeManager#getDescriptionFor(InputStream, String, QualifiedName[]) + * @since 3.0 + */ + public IContentDescription getContentDescription() throws CoreException; + + /** + * Returns an open input stream on the contents of this file. + *

                    + * This refinement of the corresponding {@link IStorage} method + * is a convenience method returning an open input stream. It's equivalent to: + *

                    +	 *   getContents(RefreshManager#PREF_LIGHTWEIGHT_AUTO_REFRESH);
                    +	 * 
                    + *

                    + *

                    + * If lightweight auto-refresh is not enabled this method will throw a CoreException + * when opening out-of-sync resources. + *

                    + * The client is responsible for closing the stream when finished. + * + * @return an input stream containing the contents of the file + * @exception CoreException if this method fails. The status code associated with exception + * reflects the cause of the failure. Reasons include: + *
                      + *
                    • {@link IResourceStatus#RESOURCE_NOT_FOUND} - This file does not exist. + * Please notice that a successful {@link #exists()} check prior to calling + * {@link #getContents()} does not guarantee the file existence since the file may be + * deleted outside Eclipse at the very last moment.
                    • + *
                    • {@link IResourceStatus#RESOURCE_NOT_LOCAL} - This resource is not local.
                    • + *
                    • {@link IResourceStatus#RESOURCE_WRONG_TYPE} - The file-system resource is not + * a file.
                    • + *
                    • {@link IResourceStatus#OUT_OF_SYNC_LOCAL} - The workspace is not in sync with + * the corresponding location in the local file system (and + * {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is disabled).
                    • + *
                    + */ + @Override + public InputStream getContents() throws CoreException; + + /** + * This refinement of the corresponding IStorage method + * returns an open input stream on the contents of this file. + * The client is responsible for closing the stream when finished. + * If force is true the file is opened and an input + * stream returned regardless of the sync state of the file. The file + * is not synchronized with the workspace. + * If force is false the method fails if not in sync. + * + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @return an input stream containing the contents of the file + * @exception CoreException if this method fails. The status code associated with exception + * reflects the cause of the failure. Reasons include: + *
                      + *
                    • {@link IResourceStatus#RESOURCE_NOT_FOUND} - This file does not exist. + * Please notice that a successful {@link #exists()} check prior to calling + * {@link #getContents()} does not guarantee the file existence since the file may be + * deleted outside Eclipse at the very last moment.
                    • + *
                    • {@link IResourceStatus#RESOURCE_NOT_LOCAL} - This resource is not local.
                    • + *
                    • {@link IResourceStatus#RESOURCE_WRONG_TYPE} - The file-system resource is not + * a file.
                    • + *
                    • {@link IResourceStatus#OUT_OF_SYNC_LOCAL} - The workspace is not in sync with + * the corresponding location in the local file system (and + * {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is disabled).
                    • + *
                    + */ + public InputStream getContents(boolean force) throws CoreException; + + /** + * Returns a constant identifying the character encoding of this file, or + * ENCODING_UNKNOWN if it could not be determined. The returned constant + * will be one of the ENCODING_* constants defined on IFile. + * + * This method attempts to guess the file's character encoding by analyzing + * the first few bytes of the file. If no identifying pattern is found at the + * beginning of the file, ENC_UNKNOWN will be returned. This method will + * not attempt any complex analysis of the file to make a guess at the + * encoding that is used. + * + * @return The character encoding of this file + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource does not exist.
                    • + *
                    • This resource could not be read.
                    • + *
                    • This resource is not local.
                    • + *
                    • The corresponding location in the local file system + * is occupied by a directory.
                    • + *
                    + * @deprecated use {@link #getCharset} instead + */ + @Deprecated + public int getEncoding() throws CoreException; + + /** + * Returns the full path of this file. + * This refinement of the corresponding IStorage and IResource + * methods links the semantics of resource and storage object paths such that + * IFiles always have a path and that path is relative to the + * containing workspace. + * + * @see IResource#getFullPath() + * @see IStorage#getFullPath() + */ + @Override + public IPath getFullPath(); + + /** + * Returns a list of past states of this file known to this workspace. + * Recently added states first. + *

                    + * 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 + * @return an array of states of this file + * @exception CoreException if this method fails. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + */ + public IFileState[] getHistory(IProgressMonitor monitor) throws CoreException; + + /** + * Returns the name of this file. + * This refinement of the corresponding IStorage and IResource + * methods links the semantics of resource and storage object names such that + * IFiles always have a name and that name equivalent to the + * last segment of its full path. + * + * @see IResource#getName() + * @see IStorage#getName() + */ + @Override + public String getName(); + + /** + * Returns whether this file is read-only. + * This refinement of the corresponding IStorage and IResource + * methods links the semantics of read-only resources and read-only storage objects. + * + * @see IResource#isReadOnly() + * @see IStorage#isReadOnly() + */ + @SuppressWarnings("deprecation") + @Override + public boolean isReadOnly(); + + /** + * Moves this resource to be at the given location. + *

                    + * This is a convenience method, fully equivalent to: + *

                    +	 *   move(destination, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
                    +	 * 
                    + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file has been removed from its parent and a new file + * has been added to the parent of the destination. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                      + *
                    • This resource does not exist.
                    • + *
                    • This resource is not local.
                    • + *
                    • The resource corresponding to the parent destination path does not exist.
                    • + *
                    • The resource corresponding to the parent destination path is a closed + * project.
                    • + *
                    • A resource at destination path does exist.
                    • + *
                    • A resource of a different type exists at the destination path.
                    • + *
                    • This resource is out of sync with the local file system + * and force is false.
                    • + *
                    • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IResource#move(IPath,int,IProgressMonitor) + * @see IResourceRuleFactory#moveRule(IResource, IResource) + */ + public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the charset for this file. Passing a value of null + * will remove the charset setting for this resource. + * + * @param newCharset a charset name, or null + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource does not exist.
                    • + *
                    • An error happened while persisting this setting.
                    • + *
                    + * @see #getCharset() + * @since 3.0 + * @deprecated Replaced by {@link #setCharset(String, IProgressMonitor)} which + * is a workspace operation and reports changes in resource deltas. + */ + @Deprecated + public void setCharset(String newCharset) throws CoreException; + + /** + * Sets the charset for this file. Passing a value of null + * will remove the charset setting for this resource. + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's encoding has changed. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param newCharset a charset name, or null + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource does not exist.
                    • + *
                    • An error happened while persisting this setting.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See {@link IResourceChangeEvent} for more details.
                    • + *
                    + * @see #getCharset() + * @see IResourceRuleFactory#charsetRule(IResource) + * @since 3.0 + */ + public void setCharset(String newCharset, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given input stream. + *

                    + * This is a convenience method, fully equivalent to: + *

                    +	 *   setContents(source, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
                    +	 * 
                    + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's contents have been changed. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param source an input stream containing the new contents of the file + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param keepHistory a flag indicating whether or not store + * the current contents in the local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource does not exist.
                    • + *
                    • The corresponding location in the local file system + * is occupied by a directory.
                    • + *
                    • The workspace is not in sync with the corresponding location + * in the local file system and force is false.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    • The file modification validator disallowed the change.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #setContents(java.io.InputStream,int,IProgressMonitor) + */ + public void setContents(InputStream source, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given file state. + *

                    + * This is a convenience method, fully equivalent to: + *

                    +	 *   setContents(source, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
                    +	 * 
                    + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param source a previous state of this resource + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param keepHistory a flag indicating whether or not store + * the current contents in the local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource does not exist.
                    • + *
                    • The state does not exist.
                    • + *
                    • The corresponding location in the local file system + * is occupied by a directory.
                    • + *
                    • The workspace is not in sync with the corresponding location + * in the local file system and force is false.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    • The file modification validator disallowed the change.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #setContents(IFileState,int,IProgressMonitor) + */ + public void setContents(IFileState source, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given input stream. + * The stream will get closed whether this method succeeds or fails. + * If the stream is null then the content is set to be the + * empty sequence of bytes. + *

                    + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite a corresponding file in the local file system provided + * it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write a corresponding file in the local file system, overwriting any + * existing one if need be. In either case, if this method succeeds, the + * resource will be marked as being local (even if it wasn't before). + *

                    + *

                    + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of this file should be captured in the workspace's local + * history (properties are not recorded in the local history). The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying KEEP_HISTORY + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. This flag is ignored if the file was not previously local. + *

                    + *

                    + * Update flags other than FORCE and KEEP_HISTORY + * are ignored. + *

                    + *

                    + * Prior to modifying the contents of this file, the file modification validator (if provided + * by the VCM plug-in), will be given a chance to perform any last minute preparations. Validation + * is performed by calling IFileModificationValidator.validateSave on this file. + * If the validation fails, then this operation will fail. + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param source an input stream containing the new contents of the file + * @param updateFlags bit-wise or of update flag constants + * (FORCE and KEEP_HISTORY) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource does not exist.
                    • + *
                    • The corresponding location in the local file system + * is occupied by a directory.
                    • + *
                    • The workspace is not in sync with the corresponding location + * in the local file system and FORCE is not specified.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    • The file modification validator disallowed the change.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void setContents(InputStream source, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the contents of this file to the bytes in the given file state. + *

                    + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite a corresponding file in the local file system provided + * it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write a corresponding file in the local file system, overwriting any + * existing one if need be. In either case, if this method succeeds, the + * resource will be marked as being local (even if it wasn't before). + *

                    + *

                    + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of this file should be captured in the workspace's local + * history (properties are not recorded in the local history). The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying KEEP_HISTORY + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. This flag is ignored if the file was not previously local. + *

                    + *

                    + * Update flags other than FORCE and KEEP_HISTORY + * are ignored. + *

                    + *

                    + * Prior to modifying the contents of this file, the file modification validator (if provided + * by the VCM plug-in), will be given a chance to perform any last minute preparations. Validation + * is performed by calling IFileModificationValidator.validateSave on this file. + * If the validation fails, then this operation will fail. + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this file's content have been changed. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param source a previous state of this resource + * @param updateFlags bit-wise or of update flag constants + * (FORCE and KEEP_HISTORY) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource does not exist.
                    • + *
                    • The state does not exist.
                    • + *
                    • The corresponding location in the local file system + * is occupied by a directory.
                    • + *
                    • The workspace is not in sync with the corresponding location + * in the local file system and FORCE is not specified.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    • The file modification validator disallowed the change.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void setContents(IFileState source, int updateFlags, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java new file mode 100644 index 0000000000..7880447c2c --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileModificationValidator.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * 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; + +import org.eclipse.core.resources.team.FileModificationValidator; +import org.eclipse.core.runtime.IStatus; + +/** + * The file modification validator is a Team-related hook for pre-checking operations + * that modify the contents of files. + *

                    + * This interface is used only in conjunction with the + * "org.eclipse.core.resources.fileModificationValidator" + * extension point. It is intended to be implemented only + * by the Eclipse Platform Team plug-in. + *

                    + * + * @since 2.0 + * @deprecated clients should subclass {@link FileModificationValidator} instead + * of implementing this interface + */ +@Deprecated +public interface IFileModificationValidator { + /** + * Validates that the given files can be modified. The files must all exist + * in the workspace. The optional context object may be supplied if + * UI-based validation is required. If the context is null, the + * validator must attempt to perform the validation in a headless manner. + * The returned status is IStatus.OK if this validator + * believes the given file can be modified. Other return statuses indicate + * the reason why the individual files cannot be modified. + * + * @param files the files that are to be modified; these files must all exist in the workspace + * @param context the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null if there is no UI context (declared + * as an Object to avoid any direct references on the SWT component) + * @return a status object that is OK if things are fine, otherwise a status describing + * reasons why modifying the given files is not reasonable + * @see IWorkspace#validateEdit(IFile[], Object) + */ + public IStatus validateEdit(IFile[] files, Object context); + + /** + * Validates that the given file can be saved. This method is called from + * IFile#setContents and IFile#appendContents + * before any attempt to write data to disk. The returned status is + * IStatus.OK if this validator believes the given file can be + * successfully saved. In all other cases the return value is a non-OK status. + * Note that a return value of IStatus.OK does not guarantee + * that the save will succeed. + * + * @param file the file that is to be modified; this file must exist in the workspace + * @return a status indicating whether or not it is reasonable to try writing to the given file; + * IStatus.OK indicates a save should be attempted. + * + * @see IFile#setContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + * @see IFile#appendContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus validateSave(IFile file); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java new file mode 100644 index 0000000000..bf354eaf49 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.resources; + +import java.io.InputStream; +import org.eclipse.core.runtime.*; + +/** + * A previous state of a file stored in the workspace's local history. + *

                    + * Certain methods for updating, deleting, or moving a file cause the + * "before" contents of the file to be copied to an internal area of the + * workspace called the local history area thus providing + * a limited history of earlier states of a file. + *

                    + *

                    + * Moving or copying a file will cause a copy of its local history to appear + * at the new location as well as at the original location. Subsequent + * changes to either file will only affect the local history of the file + * changed. Deleting a file and creating another one at the + * same path does not affect the history. If the original file had + * history, that same history will be available for the new one. + *

                    + *

                    + * The local history does not track resource properties. + * File states are volatile; the platform does not guarantee that a + * certain state will always be in the local history. + *

                    + *

                    + * File state objects implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

                    + * + * @see IFile + * @see IStorage + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFileState extends IEncodedStorage, IAdaptable { + /** + * Returns whether this file state still exists in the local history. + * + * @return true if this state exists, and false + * if it does not + */ + public boolean exists(); + + /** + * Returns an open input stream on the contents of this file state. + * This refinement of the corresponding + * IStorage method returns an open input stream + * on the contents this file state represents. + * The client is responsible for closing the stream when finished. + * + * @return an input stream containing the contents of the file + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This state does not exist.
                    • + *
                    + */ + @Override + public InputStream getContents() throws CoreException; + + /** + * Returns the full path of this file state. + * This refinement of the corresponding IStorage + * method specifies that IFileStates always have a + * path and that path is the full workspace path of the file represented by this state. + * + * @see IResource#getFullPath() + * @see IStorage#getFullPath() + */ + @Override + public IPath getFullPath(); + + /** + * Returns the modification time of the file. If you create a file at + * 9:00 and modify it at 11:00, the file state added to the history + * at 11:00 will have 9:00 as its modification time. + *

                    + * Note that is used only to give the user a general idea of how + * old this file state is. + * + * @return the time of last modification, in milliseconds since + * January 1, 1970, 00:00:00 GMT. + */ + public long getModificationTime(); + + /** + * Returns the name of this file state. + * This refinement of the corresponding IStorage + * method specifies that IFileStates always have a + * name and that name is equivalent to the last segment of the full path + * of the resource represented by this state. + * + * @see IResource#getName() + * @see IStorage#getName() + */ + @Override + public String getName(); + + /** + * Returns whether this file state is read-only. + * This refinement of the corresponding + * IStorage method restricts IFileStates to + * always be read-only. + * + * @see IStorage + */ + @Override + public boolean isReadOnly(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java new file mode 100644 index 0000000000..2b288af079 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFilterMatcherDescriptor.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2009 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 + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.resources.filtermatchers.AbstractFileInfoMatcher; + +/** + * A filter descriptor contains information about a filter type + * obtained from the plug-in manifest (plugin.xml) files. + *

                    + * Filter descriptors are platform-defined objects that exist + * independent of whether that filter's bundle has been started. + *

                    + * + * @see AbstractFileInfoMatcher + * @see IWorkspace#getFilterMatcherDescriptor(String) + * @see IWorkspace#getFilterMatcherDescriptors() + * @since 3.6 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFilterMatcherDescriptor { + + /** + * An argument filter type constant (value "filter"), denoting that this + * filter takes another filter as argument. + */ + public static final String ARGUMENT_TYPE_FILTER_MATCHER = "filterMatcher"; //$NON-NLS-1$ + /** + * An argument filter type constant (value "filters"), denoting that this + * filter takes an array of other filters as argument. + */ + public static final String ARGUMENT_TYPE_FILTER_MATCHERS = "filterMatchers"; //$NON-NLS-1$ + /** + * An argument filter type constant (value "none"), denoting that this + * filter does not take any arguments. + */ + public static final String ARGUMENT_TYPE_NONE = "none"; //$NON-NLS-1$ + /** + * An argument filter type constant (value "string"), denoting that this + * filter takes a string argument + */ + public static final String ARGUMENT_TYPE_STRING = "string"; //$NON-NLS-1$ + + /** + * Returns the argument type expected by this filter. The result will be one of the + * ARGUMENT_TYPE_* constants declared on this class. + * @return The argument type of this filter extension + */ + public abstract String getArgumentType(); + + /** + * Returns a translated, human-readable description for this filter extension. + * @return The human-readable filter description + */ + public abstract String getDescription(); + + /** + * Returns the fully qualified id of the filter extension. + * @return The fully qualified id of the filter extension. + */ + public abstract String getId(); + + /** + * Returns a translated, human-readable name for this filter extension. + * @return The human-readable filter name + */ + public abstract String getName(); + + /** + * TODO What is this? + */ + public abstract boolean isFirstOrdering(); + +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java new file mode 100644 index 0000000000..162305ef73 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java @@ -0,0 +1,461 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import org.eclipse.core.runtime.*; + +/** + * Folders may be leaf or non-leaf resources and may contain files and/or other folders. + * A folder resource is stored as a directory in the local file system. + *

                    + * Folders, like other resource types, may exist in the workspace but + * not be local; non-local folder resources serve as place-holders for + * folders whose properties have not yet been fetched from a repository. + *

                    + *

                    + * Folders implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

                    + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IFolder extends IContainer, IAdaptable { + + /** + * Creates a new folder resource as a member of this handle's parent resource. + *

                    + * This is a convenience method, fully equivalent to: + *

                    +	 *   create((force ? FORCE : IResource.NONE), local, monitor);
                    +	 * 
                    + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param force a flag controlling how to deal with resources that + * are not in sync with the local file system + * @param local a flag controlling whether or not the folder will be local + * after the creation + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource already exists in the workspace.
                    • + *
                    • The workspace contains a resource of a different type + * at the same path as this resource.
                    • + *
                    • The parent of this resource does not exist.
                    • + *
                    • The parent of this resource is a project that is not open.
                    • + *
                    • The parent contains a resource of a different type + * at the same path as this resource.
                    • + *
                    • The parent of this resource is virtual, but this resource is not.
                    • + *
                    • The name of this resource is not valid (according to + * IWorkspace.validateName).
                    • + *
                    • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
                    • + *
                    • The corresponding location in the local file system is occupied + * by a folder and force is false.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IFolder#create(int,boolean,IProgressMonitor) + */ + public void create(boolean force, boolean local, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new folder resource as a member of this handle's parent resource. + *

                    + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to create a directory in the local file system if there isn't one already. + * This option ensures there is no unintended data loss; it is the recommended + * setting. However, if FORCE is specified, this method will + * be deemed a success even if there already is a corresponding directory. + *

                    + *

                    + * The {@link IResource#DERIVED} update flag indicates that this resource + * should immediately be set as a derived resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setDerived(boolean)} + * with a value of true immediately after creating the resource. + *

                    + *

                    + * The {@link IResource#TEAM_PRIVATE} update flag indicates that this resource + * should immediately be set as a team private resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setTeamPrivateMember(boolean)} + * with a value of true immediately after creating the resource. + *

                    + *

                    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

                    + *

                    + * Update flags other than those listed above are ignored. + *

                    + *

                    + * This method synchronizes this resource with the local file system. + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#FORCE}, {@link IResource#DERIVED}, {@link IResource#TEAM_PRIVATE}) + * and {@link IResource#VIRTUAL}) + * @param local a flag controlling whether or not the folder will be local + * after the creation + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource already exists in the workspace.
                    • + *
                    • The workspace contains a resource of a different type + * at the same path as this resource.
                    • + *
                    • The parent of this resource does not exist.
                    • + *
                    • The parent of this resource is a project that is not open.
                    • + *
                    • The parent contains a resource of a different type + * at the same path as this resource.
                    • + *
                    • The parent of this resource is virtual, but this resource is not.
                    • + *
                    • The name of this resource is not valid (according to + * IWorkspace.validateName).
                    • + *
                    • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
                    • + *
                    • The corresponding location in the local file system is occupied + * by a folder and FORCE is not specified.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#createRule(IResource) + * @since 2.0 + */ + public void create(int updateFlags, boolean local, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new folder resource as a member of this handle's parent resource. + * The folder's contents will be located in the directory specified by the given + * file system path. The given path must be either an absolute file system + * path, or a relative path whose first segment is the name of a workspace path + * variable. + *

                    + * The ALLOW_MISSING_LOCAL update flag controls how this + * method deals with cases where the local file system directory to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If ALLOW_MISSING_LOCAL is specified, the operation will succeed + * even if the local directory is missing, or the path is relative to an + * undefined variable. If ALLOW_MISSING_LOCAL is not specified, the + * operation will fail in the case where the local file system directory does + * not exist or the path is relative to an undefined variable. + *

                    + *

                    + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then the existing linked resource's location is replaced + * by localLocation's value. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

                    + *

                    + * The {@link IResource#BACKGROUND_REFRESH} update flag controls how + * this method synchronizes the new resource with the filesystem. If this flag is + * specified, resources on disk will be synchronized in the background after the + * method returns. Child resources of the link may not be available until + * this background refresh completes. If this flag is not specified, resources are + * synchronized in the foreground before this method returns. + *

                    + *

                    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

                    + *

                    + * Update flags other than those listed above are ignored. + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param localLocation a file system path where the folder should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE}, {@link IResource#BACKGROUND_REFRESH}, and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource already exists in the workspace.
                    • + *
                    • The workspace contains a resource of a different type + * at the same path as this resource.
                    • + *
                    • The parent of this resource does not exist.
                    • + *
                    • The parent of this resource is not an open project
                    • + *
                    • The name of this resource is not valid (according to + * IWorkspace.validateName).
                    • + *
                    • The corresponding location in the local file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
                    • + *
                    • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    • The team provider for the project which contains this folder does not permit + * linked resources.
                    • + *
                    • This folder's project contains a nature which does not permit linked resources.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#BACKGROUND_REFRESH + * @see IResource#HIDDEN + * @since 2.1 + */ + public void createLink(IPath localLocation, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new folder resource as a member of this handle's parent resource. + * The folder's contents will be located in the directory specified by the given + * file system URI. The given URI must be either absolute, or a relative URI + * whose first path segment is the name of a workspace path variable. + *

                    + * The ALLOW_MISSING_LOCAL update flag controls how this + * method deals with cases where the local file system directory to be linked does + * not exist, or is relative to a workspace path variable that is not defined. + * If ALLOW_MISSING_LOCAL is specified, the operation will succeed + * even if the local directory is missing, or the path is relative to an + * undefined variable. If ALLOW_MISSING_LOCAL is not specified, the + * operation will fail in the case where the local file system directory does + * not exist or the path is relative to an undefined variable. + *

                    + *

                    + * The {@link IResource#REPLACE} update flag controls how this + * method deals with cases where a resource of the same name as the + * prospective link already exists. If {@link IResource#REPLACE} + * is specified, then any existing resource with the same name is removed + * from the workspace to make way for creation of the link. This does not + * cause the underlying file system contents of that resource to be deleted. + * If {@link IResource#REPLACE} is not specified, this method will + * fail if an existing resource exists of the same name. + *

                    + *

                    + * The {@link IResource#BACKGROUND_REFRESH} update flag controls how + * this method synchronizes the new resource with the filesystem. If this flag is + * specified, resources on disk will be synchronized in the background after the + * method returns. Child resources of the link may not be available until + * this background refresh completes. If this flag is not specified, resources are + * synchronized in the foreground before this method returns. + *

                    + *

                    + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

                    + *

                    + * Update flags other than those listed above are ignored. + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the folder has been added to its parent. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param location a file system path where the folder should be linked + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE}, {@link IResource#BACKGROUND_REFRESH}, and {@link IResource#HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource already exists in the workspace.
                    • + *
                    • The workspace contains a resource of a different type + * at the same path as this resource.
                    • + *
                    • The parent of this resource does not exist.
                    • + *
                    • The parent of this resource is not an open project
                    • + *
                    • The name of this resource is not valid (according to + * IWorkspace.validateName).
                    • + *
                    • The corresponding location in the local file system does not exist, or + * is relative to an undefined variable, and ALLOW_MISSING_LOCAL is + * not specified.
                    • + *
                    • The corresponding location in the local file system is occupied + * by a file (as opposed to a directory).
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    • The team provider for the project which contains this folder does not permit + * linked resources.
                    • + *
                    • This folder's project contains a nature which does not permit linked resources.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#isLinked() + * @see IResource#ALLOW_MISSING_LOCAL + * @see IResource#REPLACE + * @see IResource#BACKGROUND_REFRESH + * @see IResource#HIDDEN + * @since 3.2 + */ + public void createLink(URI location, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this resource from the workspace. + *

                    + * This is a convenience method, fully equivalent to: + *

                    +	 *   delete((keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
                    +	 * 
                    + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this folder has been removed from its parent. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                      + *
                    • This resource could not be deleted for some reason.
                    • + *
                    • This resource is out of sync with the local file system + * and force is false.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IResourceRuleFactory#deleteRule(IResource) + * @see IResource#delete(int,IProgressMonitor) + */ + public void delete(boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Returns a handle to the file with the given name in this folder. + *

                    + * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

                    + * + * @param name the string name of the member file + * @return the (handle of the) member file + * @see #getFolder(String) + */ + public IFile getFile(String name); + + /** + * Returns a handle to the folder with the given name in this folder. + *

                    + * This is a resource handle operation; neither the container + * nor the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

                    + * + * @param name the string name of the member folder + * @return the (handle of the) member folder + * @see #getFile(String) + */ + public IFolder getFolder(String name); + + /** + * Moves this resource so that it is located at the given path. + *

                    + * This is a convenience method, fully equivalent to: + *

                    +	 *   move(destination, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
                    +	 * 
                    + *

                    + *

                    + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this folder has been removed from its parent and a new folder + * has been added to the parent of the destination. + *

                    + *

                    + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                    + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag controlling whether files under this folder + * should be stored in the workspace's local history + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                      + *
                    • This resource does not exist.
                    • + *
                    • This resource or one of its descendents is not local.
                    • + *
                    • The resource corresponding to the parent destination path does not exist.
                    • + *
                    • The resource corresponding to the parent destination path is a closed + * project.
                    • + *
                    • A resource at destination path does exist.
                    • + *
                    • A resource of a different type exists at the destination path.
                    • + *
                    • This resource or one of its descendents is out of sync with the local file system + * and force is false.
                    • + *
                    • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
                    • + *
                    • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                    • + *
                    + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @see IResource#move(IPath,int,IProgressMonitor) + */ + public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java new file mode 100644 index 0000000000..7672452315 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarker.java @@ -0,0 +1,578 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Map; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; + +/** + * Markers are a general mechanism for associating notes and meta-data with + * resources. + *

                    + * Markers themselves are handles in the same way as IResources + * are handles. Instances of IMarker do not hold the attributes + * themselves but rather uniquely refer to the attribute container. As such, + * their state may change underneath the handle with no warning to the holder + * of the handle. + *

                    + * The Resources plug-in provides a general framework for + * defining and manipulating markers and provides several standard marker types. + *

                    + *

                    + * Each marker has:

                      + *
                    • a type string, specifying its type (e.g. + * "org.eclipse.core.resources.taskmarker"),
                    • + *
                    • an identifier which is unique (relative to a particular resource)
                    • + *
                    + * Specific types of markers may carry additional information. + *

                    + *

                    + * The resources plug-in defines five standard types: + *

                      + *
                    • org.eclipse.core.resources.marker
                    • + *
                    • org.eclipse.core.resources.taskmarker
                    • + *
                    • org.eclipse.core.resources.problemmarker
                    • + *
                    • org.eclipse.core.resources.bookmark
                    • + *
                    • org.eclipse.core.resources.textmarker
                    • + *
                    + * The plug-in also provides an extension point ( + * org.eclipse.core.resources.markers) into which other + * plug-ins can install marker type declaration extensions. + *

                    + *

                    + * Marker types are declared within a multiple inheritance type system. + * New markers are defined in the plugin.xml file of the + * declaring plug-in. A valid declaration contains elements as defined by + * the extension point DTD: + *

                      + *
                    • type - the unique name of the marker type
                    • + *
                    • super - the list of marker types of which this marker is to be considered a sub-type
                    • + *
                    • attributes - the list of standard attributes which may be present on this type of marker
                    • + *
                    • persistent - whether markers of this type should be persisted by the platform
                    • + * + *

                      + *

                      All markers declared as persistent are saved when the + * workspace is saved, except those explicitly set as transient (the + * TRANSIENT attribute is set as true). A plug-in + * which defines a persistent marker is not directly involved in saving and + * restoring the marker. Markers are not under version and configuration + * management, and cannot be shared via VCM repositories. + *

                      + *

                      + * Markers implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

                      + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IMarker extends IAdaptable { + + /*==================================================================== + * Marker types: + *====================================================================*/ + + /** + * Base marker type. + * + * @see #getType() + */ + public static final String MARKER = ResourcesPlugin.PI_RESOURCES + ".marker"; //$NON-NLS-1$ + + /** + * Task marker type. + * + * @see #getType() + */ + public static final String TASK = ResourcesPlugin.PI_RESOURCES + ".taskmarker"; //$NON-NLS-1$ + + /** + * Problem marker type. + * + * @see #getType() + */ + public static final String PROBLEM = ResourcesPlugin.PI_RESOURCES + ".problemmarker"; //$NON-NLS-1$ + + /** + * Text marker type. + * + * @see #getType() + */ + public static final String TEXT = ResourcesPlugin.PI_RESOURCES + ".textmarker"; //$NON-NLS-1$ + + /** + * Bookmark marker type. + * + * @see #getType() + */ + public static final String BOOKMARK = ResourcesPlugin.PI_RESOURCES + ".bookmark"; //$NON-NLS-1$ + + /*==================================================================== + * Marker attributes: + *====================================================================*/ + + /** + * Severity marker attribute. A number from the set of error, warning and info + * severities defined by the platform. + * + * @see #SEVERITY_ERROR + * @see #SEVERITY_WARNING + * @see #SEVERITY_INFO + * @see #getAttribute(String, int) + */ + public static final String SEVERITY = "severity"; //$NON-NLS-1$ + + /** + * Message marker attribute. A localized string describing the nature + * of the marker (e.g., a name for a bookmark or task). The content + * and form of this attribute is not specified or interpreted by the platform. + * + * @see #getAttribute(String, String) + */ + public static final String MESSAGE = "message"; //$NON-NLS-1$ + + /** + * Location marker attribute. The location is a human-readable (localized) string which + * can be used to distinguish between markers on a resource. As such it + * should be concise and aimed at users. The content and + * form of this attribute is not specified or interpreted by the platform. + * + * @see #getAttribute(String, String) + */ + public static final String LOCATION = "location"; //$NON-NLS-1$ + + /** + * Priority marker attribute. A number from the set of high, normal and low + * priorities defined by the platform. + * + * @see #PRIORITY_HIGH + * @see #PRIORITY_NORMAL + * @see #PRIORITY_LOW + * @see #getAttribute(String, int) + */ + public static final String PRIORITY = "priority"; //$NON-NLS-1$ + + /** + * Done marker attribute. A boolean value indicating whether + * the marker (e.g., a task) is considered done. + * + * @see #getAttribute(String, String) + */ + public static final String DONE = "done"; //$NON-NLS-1$ + + /** + * Character start marker attribute. An integer value indicating where a text + * marker starts. This attribute is zero-relative and inclusive. + * + * @see #getAttribute(String, String) + */ + public static final String CHAR_START = "charStart"; //$NON-NLS-1$ + + /** + * Character end marker attribute. An integer value indicating where a text + * marker ends. This attribute is zero-relative and exclusive. + * + * @see #getAttribute(String, String) + */ + public static final String CHAR_END = "charEnd"; //$NON-NLS-1$ + + /** + * Line number marker attribute. An integer value indicating the line number + * for a text marker. This attribute is 1-relative. + * + * @see #getAttribute(String, String) + */ + public static final String LINE_NUMBER = "lineNumber"; //$NON-NLS-1$ + + /** + * Transient marker attribute. A boolean value indicating whether the + * marker (e. g., a task) is considered transient even if its type is + * declared as persistent. + * + * @see #getAttribute(String, String) + * @since 2.1 + */ + public static final String TRANSIENT = "transient"; //$NON-NLS-1$ + + /** + * User editable marker attribute. A boolean value indicating whether a + * user should be able to manually change the marker (e.g. a task). The + * default is true. Note that the value of this attribute + * is to be used by the UI as a suggestion and its value will NOT be + * interpreted by Core in any manner and will not be enforced by Core + * when performing any operations on markers. + * + * @see #getAttribute(String, String) + * @since 2.1 + */ + public static final String USER_EDITABLE = "userEditable"; //$NON-NLS-1$ + + /** + * Source id attribute. A string attribute that can be used by tools that + * generate markers to indicate the source of the marker. Use of this attribute is + * optional and its format or existence is not enforced. This attribute is + * intended to improve serviceability by providing a value that product support + * personnel or automated tools can use to determine appropriate help and + * resolutions for markers. + * + * @see #getAttribute(String, String) + * @since 3.3 + */ + public static final String SOURCE_ID = "sourceId"; //$NON-NLS-1$ + + /*==================================================================== + * Marker attributes values: + *====================================================================*/ + + /** + * High priority constant (value 2). + * + * @see #getAttribute(String, int) + */ + public static final int PRIORITY_HIGH = 2; + + /** + * Normal priority constant (value 1). + * + * @see #getAttribute(String, int) + */ + public static final int PRIORITY_NORMAL = 1; + + /** + * Low priority constant (value 0). + * + * @see #getAttribute(String, int) + */ + public static final int PRIORITY_LOW = 0; + + /** + * Error severity constant (value 2) indicating an error state. + * + * @see #getAttribute(String, int) + */ + public static final int SEVERITY_ERROR = 2; + + /** + * Warning severity constant (value 1) indicating a warning. + * + * @see #getAttribute(String, int) + */ + public static final int SEVERITY_WARNING = 1; + + /** + * Info severity constant (value 0) indicating information only. + * + * @see #getAttribute(String, int) + */ + public static final int SEVERITY_INFO = 0; + + /** + * Deletes this marker from its associated resource. This method has no + * effect if this marker does not exist. + * + * @exception CoreException if this marker could not be deleted. Reasons include: + *
                        + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void delete() throws CoreException; + + /** + * Tests this marker for equality with the given object. + * Two markers are equal if their id and resource are both equal. + * + * @param object the other object + * @return an indication of whether the objects are equal + */ + @Override + public boolean equals(Object object); + + /** + * Returns whether this marker exists in the workspace. A marker + * exists if its resource exists and has a marker with the marker's id. + * + * @return true if this marker exists, otherwise + * false + */ + public boolean exists(); + + /** + * Returns the attribute with the given name. The result is an instance of one + * of the following classes: String, Integer, + * or Boolean. + * Returns null if the attribute is undefined. + * + * @param attributeName the name of the attribute + * @return the value, or null if the attribute is undefined. + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This marker does not exist.
                      • + *
                      + */ + public Object getAttribute(String attributeName) throws CoreException; + + /** + * Returns the integer-valued attribute with the given name. + * Returns the given default value if the attribute is undefined. + * or the marker does not exist or is not an integer value. + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if no value is found + * @return the value or the default value if no value was found. + */ + public int getAttribute(String attributeName, int defaultValue); + + /** + * Returns the string-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or the marker does not exist or is not a string value. + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if no value is found + * @return the value or the default value if no value was found. + */ + public String getAttribute(String attributeName, String defaultValue); + + /** + * Returns the boolean-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or the marker does not exist or is not a boolean value. + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if no value is found + * @return the value or the default value if no value was found. + */ + public boolean getAttribute(String attributeName, boolean defaultValue); + + /** + * Returns a map with all the attributes for the marker. + * If the marker has no attributes then null is returned. + * + * @return a map of attribute keys and values (key type : String + * value type : String, Integer, or + * Boolean) or null. + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This marker does not exist.
                      • + *
                      + */ + public Map getAttributes() throws CoreException; + + /** + * Returns the attributes with the given names. The result is an an array + * whose elements correspond to the elements of the given attribute name + * array. Each element is null or an instance of one + * of the following classes: String, Integer, + * or Boolean. + * + * @param attributeNames the names of the attributes + * @return the values of the given attributes. + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This marker does not exist.
                      • + *
                      + */ + public Object[] getAttributes(String[] attributeNames) throws CoreException; + + /** + * Returns the time at which this marker was created. + * + * @return the difference, measured in milliseconds, between the time at which + * this marker was created and midnight, January 1, 1970 UTC, or 0L + * if the creation time is not known (this can occur in workspaces created using v2.0 or earlier). + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This marker does not exist.
                      • + *
                      + * @since 2.1 + */ + public long getCreationTime() throws CoreException; + + /** + * Returns the id of the marker. The id of a marker is unique + * relative to the resource with which the marker is associated. + * Marker ids are not globally unique. + * + * @return the id of the marker + * @see IResource#findMarker(long) + */ + public long getId(); + + /** + * Returns the resource with which this marker is associated. + * + * @return the resource with which this marker is associated + */ + public IResource getResource(); + + /** + * Returns the type of this marker. The returned marker type will not be + * null. + * + * @return the type of this marker + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This marker does not exist.
                      • + *
                      + */ + public String getType() throws CoreException; + + /** + * Returns whether the type of this marker is considered to be a sub-type of + * the given marker type. + * + * @return boolean trueif the marker's type + * is the same as (or a sub-type of) the given type. + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This marker does not exist.
                      • + *
                      + */ + public boolean isSubtypeOf(String superType) throws CoreException; + + /** + * Sets the integer-valued attribute with the given name. + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

                      + * + * @param attributeName the name of the attribute + * @param value the value + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This marker does not exist.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttribute(String attributeName, int value) throws CoreException; + + /** + * Sets the attribute with the given name. The value must be null or + * an instance of one of the following classes: + * String, Integer, or Boolean. + * If the value is null, the attribute is considered to be undefined. + * + *

                      + * The attribute value cannot be String + * whose UTF encoding exceeds 65535 bytes. On persistent + * markers this limit is enforced by an assertion. + *

                      + * + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

                      + * + * @param attributeName the name of the attribute + * @param value the value, or null if the attribute is to be undefined + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This marker does not exist.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttribute(String attributeName, Object value) throws CoreException; + + /** + * Sets the boolean-valued attribute with the given name. + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

                      + * + * @param attributeName the name of the attribute + * @param value the value + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This marker does not exist.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttribute(String attributeName, boolean value) throws CoreException; + + /** + * Sets the given attribute key-value pairs on this marker. + * The values must be null or an instance of + * one of the following classes: String, + * Integer, or Boolean. + * If a value is null, the new value of the + * attribute is considered to be undefined. + * + *

                      + * The values of the attributes cannot be String + * whose UTF encoding exceeds 65535 bytes. On persistent markers + * this limit is enforced by an assertion. + *

                      + * + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

                      + * + * @param attributeNames an array of attribute names + * @param values an array of attribute values + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This marker does not exist.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttributes(String[] attributeNames, Object[] values) throws CoreException; + + /** + * Sets the attributes for this marker to be the ones contained in the + * given table. The values must be an instance of one of the following classes: + * String, Integer, or Boolean. + * Attributes previously set on the marker but not included in the given map + * are considered to be removals. Setting the given map to be null + * is equivalent to removing all marker attributes. + * + *

                      + * The values of the attributes cannot be String + * whose UTF encoding exceeds 65535 bytes. On persistent markers + * this limit is enforced by an assertion. + *

                      + * + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this marker has been modified. + *

                      + * + * @param attributes a map of attribute names to attribute values + * (key type : String value type : String, + * Integer, or Boolean) or null + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This marker does not exist.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void setAttributes(Map attributes) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java new file mode 100644 index 0000000000..9c2e642233 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IMarkerDelta.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * 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.resources; + +import java.util.Map; + +/** + * A marker delta describes the change to a single marker. + * A marker can either be added, removed or changed. + * Marker deltas give access to the state of the marker as it + * was (in the case of deletions and changes) before the modifying + * operation occurred. + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IMarkerDelta { + /** + * Returns the object attribute with the given name. The result is an instance of one + * of the following classes: String, Integer, + * or Boolean. + * Returns null if the attribute is undefined. + * The set of valid attribute names is defined elsewhere. + *

                      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

                      + * @param attributeName the name of the attribute + * @return the value, or null if the attribute is undefined. + */ + public Object getAttribute(String attributeName); + + /** + * Returns the integer-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or is not an integer value. + *

                      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

                      + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if the attribute does not exist + * @return the value or the default value if the attribute is undefined. + */ + public int getAttribute(String attributeName, int defaultValue); + + /** + * Returns the string-valued attribute with the given name. + * Returns the given default value if the attribute is undefined or + * is not a string value. + *

                      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

                      + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if the attribute does not exist + * @return the value or the default value if the attribute is undefined. + */ + public String getAttribute(String attributeName, String defaultValue); + + /** + * Returns the boolean-valued attribute with the given name. + * Returns the given default value if the attribute is undefined + * or is not a boolean value. + *

                      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

                      + * + * @param attributeName the name of the attribute + * @param defaultValue the value to use if the attribute does not exist + * @return the value or the default value if the attribute is undefined. + */ + public boolean getAttribute(String attributeName, boolean defaultValue); + + /** + * Returns a Map with all the attributes for the marker. The result is a Map + * whose keys are attributes names and whose values are attribute values. + * Each value an instance of one of the following classes: String, + * Integer, or Boolean. If the marker has no + * attributes then null is returned. + *

                      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

                      + * + * @return a map of attribute keys and values (key type : String + * value type : String, Integer, or + * Boolean) or null. + */ + public Map getAttributes(); + + /** + * Returns the attributes with the given names. The result is an array + * whose elements correspond to the elements of the given attribute name + * array. Each element is null or an instance of one + * of the following classes: String, Integer, + * or Boolean. + *

                      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

                      + * + * @param attributeNames the names of the attributes + * @return the values of the given attributes. + */ + public Object[] getAttributes(String[] attributeNames); + + /** + * Returns the id of the marker. The id of a marker is unique + * relative to the resource with which the marker is associated. + * Marker ids are not globally unique. + * + * @return the id of the marker + */ + public long getId(); + + /** + * Returns the kind of this marker delta: + * one of IResourceDelta.ADDED, + * IResourceDelta.REMOVED, or IResourceDelta.CHANGED. + * + * @return the kind of marker delta + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + */ + public int getKind(); + + /** + * Returns the marker described by this change. + * If kind is IResourceDelta.REMOVED, then this is the old marker, + * otherwise this is the new marker. Note that if the marker was deleted, + * the value returned cannot be used to access attributes. + * + * @return the marker + */ + public IMarker getMarker(); + + /** + * Returns the resource with which this marker is associated. + * + * @return the resource + */ + public IResource getResource(); + + /** + * Returns the type of this marker. + *

                      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

                      + * + * @return the type of this marker + */ + public String getType(); + + /** + * Returns whether the type of this marker is considered to be a sub-type of + * the given marker type. + *

                      + * If kind is IResourceDelta.ADDED, then the information is + * from the new marker, otherwise it is from the old marker. + *

                      + * + * @return boolean trueif the marker's type + * is the same as (or a sub-type of) the given type. + */ + public boolean isSubtypeOf(String superType); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java new file mode 100644 index 0000000000..c0138248e4 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; + +/** + * Describes a change in a path variable. The change may denote that a + * variable has been created, deleted or had its value changed. + * + * @since 2.1 + * @see IPathVariableChangeListener + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IPathVariableChangeEvent { + + /** Event type constant (value = 1) that denotes a value change . */ + public final static int VARIABLE_CHANGED = 1; + + /** Event type constant (value = 2) that denotes a variable creation. */ + public final static int VARIABLE_CREATED = 2; + + /** Event type constant (value = 3) that denotes a variable deletion. */ + public final static int VARIABLE_DELETED = 3; + + /** + * Returns the variable's current value. If the event type is + * VARIABLE_CHANGED then it is the new value, if the event + * type is VARIABLE_CREATED then it is the new value, or + * if the event type is VARIABLE_DELETED then it will + * be null. + * + * @return the variable's current value, or null + */ + public IPath getValue(); + + /** + * Returns the affected variable's name. + * + * @return the affected variable's name + */ + public String getVariableName(); + + /** + * Returns an object identifying the source of this event. + * + * @return an object identifying the source of this event + * @see java.util.EventObject + */ + public Object getSource(); + + /** + * Returns the type of event being reported. + * + * @return one of the event type constants + * @see #VARIABLE_CHANGED + * @see #VARIABLE_CREATED + * @see #VARIABLE_DELETED + */ + public int getType(); + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java new file mode 100644 index 0000000000..cd55441aef --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.util.EventListener; + +/** + * An interface to be implemented by objects interested in path variable + * creation, removal and value change events. + * + *

                      Clients may implement this interface.

                      + * + * @since 2.1 + */ +public interface IPathVariableChangeListener extends EventListener { + /** + * Notification that a path variable has changed. + *

                      + * This method is called when a path variable is added, removed or has its value + * changed in the observed IPathVariableManager object. + *

                      + * + * @param event the path variable change event object describing which variable + * changed and how + * @see IPathVariableManager#addChangeListener(IPathVariableChangeListener) + * @see IPathVariableManager#removeChangeListener(IPathVariableChangeListener) + * @see IPathVariableChangeEvent + */ + public void pathVariableChanged(IPathVariableChangeEvent event); + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java new file mode 100644 index 0000000000..c6c5ed92a3 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableManager.java @@ -0,0 +1,352 @@ +/******************************************************************************* + * 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] Project Path Variable Support + *******************************************************************************/ + +package org.eclipse.core.resources; + +import java.net.URI; +import org.eclipse.core.runtime.*; + +/** + * Manages a collection of path variables and resolves paths containing a + * variable reference. + *

                      + * A path variable is a pair of non-null elements (name,value) where name is + * a case-sensitive string (containing only letters, digits and the underscore + * character, and not starting with a digit), and value is an absolute + * IPath object. + *

                      + *

                      + * Path variables allow for the creation of relative paths whose exact + * location in the file system depends on the value of a variable. A variable + * reference may only appear as the first segment of a relative path. + *

                      + * + * @see org.eclipse.core.runtime.IPath + * @since 2.1 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IPathVariableManager { + + /** + * Converts an absolute path to path relative to some defined + * variable. For example, converts "C:/foo/bar.txt" into "FOO/bar.txt", + * granted that the path variable "FOO" value is "C:/foo". + *

                      + * The "force" argument will cause an intermediate path variable to be created if + * the given path can be relative only to a parent of an existing path variable. + * For example, if the path "C:/other/file.txt" is to be converted + * and no path variables point to "C:/" or "C:/other" but "FOO" + * points to "C:/foo", an intermediate "OTHER" variable will be + * created relative to "FOO" containing the value "${PARENT-1-FOO}" + * so that the final path returned will be "OTHER/file.txt". + *

                      + *

                      + * The argument "variableHint" can be used to specify the name of the path + * variable to make the provided path relative to. + *

                      + * + * @param path The absolute path to be converted + * @param force indicates whether intermediate path variables should be created + * if the path is relative only to a parent of an existing path variable. + * @param variableHint The name of the variable to which the path should be made + * relative to, or null for the nearest one. + * @return The converted path + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • The variable name is not valid
                      • + *
                      + * @since 3.6 + */ + public URI convertToRelative(URI path, boolean force, String variableHint) throws CoreException; + + /** + * Sets the path variable with the given name to be the specified value. + * Depending on the value given and if the variable is currently defined + * or not, there are several possible outcomes for this operation: + *

                      + *

                        + *
                      • A new variable will be created, if there is no variable defined with + * the given name, and the given value is not null. + *
                      • + * + *
                      • The referred variable's value will be changed, if it already exists + * and the given value is not null.
                      • + * + *
                      • The referred variable will be removed, if a variable with the given + * name is currently defined and the given value is null. + *
                      • + * + *
                      • The call will be ignored, if a variable with the given name is not + * currently defined and the given value is null, or if it is + * defined but the given value is equal to its current value. + *
                      • + *
                      + *

                      If a variable is effectively changed, created or removed by a call to + * this method, notification will be sent to all registered listeners.

                      + * + * @param name the name of the variable + * @param value the value for the variable (may be null) + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • The variable name is not valid
                      • + *
                      • The variable value is relative
                      • + *
                      + * @deprecated use {@link #setURIValue(String, URI)} instead. + */ + @Deprecated + public void setValue(String name, IPath value) throws CoreException; + + /** + * Sets the path variable with the given name to be the specified value. + * Depending on the value given and if the variable is currently defined + * or not, there are several possible outcomes for this operation: + *

                      + *

                        + *
                      • A new variable will be created, if there is no variable defined with + * the given name, and the given value is not null. + *
                      • + * + *
                      • The referred variable's value will be changed, if it already exists + * and the given value is not null.
                      • + * + *
                      • The referred variable will be removed, if a variable with the given + * name is currently defined and the given value is null. + *
                      • + * + *
                      • The call will be ignored, if a variable with the given name is not + * currently defined and the given value is null, or if it is + * defined but the given value is equal to its current value. + *
                      • + *
                      + *

                      If a variable is effectively changed, created or removed by a call to + * this method, notification will be sent to all registered listeners.

                      + * + * @param name the name of the variable + * @param value the value for the variable (may be null) + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • The variable name is not valid
                      • + *
                      • The variable value is relative
                      • + *
                      + * @since 3.6 + */ + public void setURIValue(String name, URI value) throws CoreException; + + /** + * Returns the value of the path variable with the given name. If there is + * no variable defined with the given name, returns null. + * + * @param name the name of the variable to return the value for + * @return the value for the variable, or null if there is no + * variable defined with the given name + * @deprecated use {@link #getURIValue(String)} instead. + */ + @Deprecated + public IPath getValue(String name); + + /** + * Returns the value of the path variable with the given name. If there is + * no variable defined with the given name, returns null. + * + * @param name the name of the variable to return the value for + * @return the value for the variable, or null if there is no + * variable defined with the given name + * @since 3.6 + */ + public URI getURIValue(String name); + + /** + * Returns an array containing all defined path variable names. + * + * @return an array containing all defined path variable names + */ + public String[] getPathVariableNames(); + + // Should be added for 3.6 + // public String[] getPathVariableNames(String name); + + /** + * Registers the given listener to receive notification of changes to path + * variables. The listener will be notified whenever a variable has been + * added, removed or had its value changed. Has no effect if an identical + * path variable change listener is already registered. + * + * @param listener the listener + * @see IPathVariableChangeListener + */ + public void addChangeListener(IPathVariableChangeListener listener); + + /** + * Removes the given path variable change listener from the listeners list. + * Has no effect if an identical listener is not registered. + * + * @param listener the listener + * @see IPathVariableChangeListener + */ + public void removeChangeListener(IPathVariableChangeListener listener); + + /** + * Resolves a relative URI object potentially containing a + * variable reference as its first segment, replacing the variable reference + * (if any) with the variable's value (which is a concrete absolute URI). + * If the given URI is absolute or has a non- null device then + * no variable substitution is done and that URI is returned as is. If the + * given URI is relative and has a null device, but the first + * segment does not correspond to a defined variable, then the URI is + * returned as is. + *

                      + * If the given URI is null then null will be + * returned. In all other cases the result will be non-null. + *

                      + * + * @param uri the URI to be resolved + * @return the resolved URI or null + * @since 3.2 + */ + public URI resolveURI(URI uri); + + /** + * Resolves a relative IPath object potentially containing a + * variable reference as its first segment, replacing the variable reference + * (if any) with the variable's value (which is a concrete absolute path). + * If the given path is absolute or has a non- null device then + * no variable substitution is done and that path is returned as is. If the + * given path is relative and has a null device, but the first + * segment does not correspond to a defined variable, then the path is + * returned as is. + *

                      + * If the given path is null then null will be + * returned. In all other cases the result will be non-null. + *

                      + * + *

                      + * For example, consider the following collection of path variables: + *

                      + *
                        + *
                      • TEMP = c:/temp
                      • + *
                      • BACKUP = /tmp/backup
                      • + *
                      + *

                      The following paths would be resolved as: + *

                      c:/bin => c:/bin

                      + *

                      c:TEMP => c:TEMP

                      + *

                      /TEMP => /TEMP

                      + *

                      TEMP => c:/temp

                      + *

                      TEMP/foo => c:/temp/foo

                      + *

                      BACKUP => /tmp/backup

                      + *

                      BACKUP/bar.txt => /tmp/backup/bar.txt

                      + *

                      SOMEPATH/foo => SOMEPATH/foo

                      + * + * @param path the path to be resolved + * @return the resolved path or null + * @deprecated use {@link #resolveURI(URI)} instead. + */ + @Deprecated + public IPath resolvePath(IPath path); + + /** + * Returns true if the given variable is defined and + * false otherwise. Returns false if the given + * name is not a valid path variable name. + * + * @param name the variable's name + * @return true if the variable exists, false + * otherwise + */ + public boolean isDefined(String name); + + /** + * Returns whether a variable is user defined or not. + * + * @return true if the path is user defined. + * @since 3.6 + */ + public boolean isUserDefined(String name); + + /** + * Validates the given name as the name for a path variable. A valid path + * variable name is made exclusively of letters, digits and the underscore + * character, and does not start with a digit. + * + * @param name a possibly valid path variable name + * @return a status object with code IStatus.OK if + * the given name is a valid path variable name, otherwise a status + * object indicating what is wrong with the string + * @see IStatus#OK + */ + public IStatus validateName(String name); + + /** + * Validates the given path as the value for a path variable. A path + * variable value must be a valid path that is absolute. + * + * @param path a possibly valid path variable value + * @return a status object with code IStatus.OK if the given + * path is a valid path variable value, otherwise a status object indicating + * what is wrong with the value + * @see IPath#isValidPath(String) + * @see IStatus#OK + */ + public IStatus validateValue(IPath path); + + /** + * Validates the given path as the value for a path variable. A path + * variable value must be a valid path that is absolute. + * + * @param path a possibly valid path variable value + * @return a status object with code {@link IStatus#OK} if the given + * path is a valid path variable value, otherwise a status object indicating + * what is wrong with the value + * @see IPath#isValidPath(String) + * @see IStatus#OK + * @since 3.6 + */ + public IStatus validateValue(URI path); + + /** + * Returns a variable relative path equivalent to an absolute path for a + * file or folder in the file system, according to the variables defined in + * this project PathVariableManager. The file or folder need not to exist. + * + * @param location + * a path in the local file system + * @return the corresponding variable relative path, or null + * if no such path is available + * @since 3.6 + */ + public URI getVariableRelativePathLocation(URI location); + + /** + * Converts the internal format of the linked resource location if the PARENT + * variables is used. For example, if the value is "${PARENT-2-VAR}\foo", the + * converted result is "${VAR}\..\..\foo". + * @param value the value encoded using OS string (as returned from Path.toOSString()) + * @param locationFormat indicates whether the value contains a string that is stored in the linked resource location rather than in the path variable value + * @return the converted path variable value + * @since 3.6 + */ + public String convertToUserEditableFormat(String value, boolean locationFormat); + + /** + * Converts the user editable format to the internal format. + * For example, if the value is "${VAR}\..\..\foo", the + * converted result is "${PARENT-2-VAR}\foo". + * If the string is not directly convertible to a ${PARENT-COUNT-VAR} + * syntax (for example, the editable string "${FOO}bar\..\..\"), intermediate + * path variables will be created. + * @param value the value encoded using OS string (as returned from Path.toOSString()) + * @param locationFormat indicates whether the value contains a string that is stored in the linked resource location rather than in the path variable value + * @return the converted path variable value + * @since 3.6 + */ + public String convertFromUserEditableFormat(String value, boolean locationFormat); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java new file mode 100644 index 0000000000..0ea186a2e4 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java @@ -0,0 +1,1067 @@ +/******************************************************************************* + * 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 - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import java.util.Map; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.content.IContentTypeMatcher; + +/** + * A project is a type of resource which groups resources + * into buildable, reusable units. + *

                      + * Features of projects include: + *

                        + *
                      • A project collects together a set of files and folders.
                      • + *
                      • A project's location controls where the project's resources are + * stored in the local file system.
                      • + *
                      • A project's build spec controls how building is done on the project.
                      • + *
                      • A project can carry session and persistent properties.
                      • + *
                      • A project can be open or closed; a closed project is + * passive and has a minimal in-memory footprint.
                      • + *
                      • A project can have one or more project build configurations.
                      • + *
                      • A project can carry references to other project build configurations.
                      • + *
                      • A project can have one or more project natures.
                      • + *
                      + *

                      + *

                      + * Projects implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

                      + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IProject extends IContainer, IAdaptable { + /** + * Option constant (value 1) indicating that a snapshot to be + * loaded or saved contains a resource tree (refresh information). + * @see #loadSnapshot(int, URI, IProgressMonitor) + * @see #saveSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public static final int SNAPSHOT_TREE = 1; + + /** + * Invokes the build method of the specified builder + * for this project. Does nothing if this project is closed. If this project + * has multiple builders on its build spec matching the given name, only + * the first matching builder will be run. The build is run for the project's + * active build configuration. + *

                      + * The builder name is declared in the extension that plugs in + * to the standard org.eclipse.core.resources.builders + * extension point. The arguments are builder specific. + *

                      + *

                      + * This method may change resources; these changes will be reported + * in a subsequent resource change event. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param kind the kind of build being requested. Valid values are: + *
                        + *
                      • {@link IncrementalProjectBuilder#FULL_BUILD}- indicates a full build.
                      • + *
                      • {@link IncrementalProjectBuilder#INCREMENTAL_BUILD}- indicates a incremental build.
                      • + *
                      • {@link IncrementalProjectBuilder#CLEAN_BUILD}- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states.
                      • + *
                      + * @param builderName the name of the builder + * @param args a table of builder-specific arguments keyed by argument name + * (key type: String, value type: String); + * null is equivalent to an empty map + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic {@link IResourceStatus#BUILD_FAILED} + * code, but it could also be any other status code; it might + * also be a {@link MultiStatus}. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IProjectDescription + * @see IncrementalProjectBuilder#build(int, Map, IProgressMonitor) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @see IResourceRuleFactory#buildRule() + */ + public void build(int kind, String builderName, Map args, IProgressMonitor monitor) throws CoreException; + + /** + * Builds this project. Does nothing if the project is closed. + *

                      + * Building a project involves executing the commands found + * in this project's build spec. The build is run for the project's + * active build configuration. + *

                      + *

                      + * This method may change resources; these changes will be reported + * in a subsequent resource change event. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param kind the kind of build being requested. Valid values are: + *
                        + *
                      • IncrementalProjectBuilder.FULL_BUILD - indicates a full build.
                      • + *
                      • IncrementalProjectBuilder.INCREMENTAL_BUILD - indicates an incremental build.
                      • + *
                      • CLEAN_BUILD- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states. + *
                      + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic BUILD_FAILED + * code, but it could also be any other status code; it might + * also be a multi-status. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IProjectDescription + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IResourceRuleFactory#buildRule() + */ + public void build(int kind, IProgressMonitor monitor) throws CoreException; + + /** + * Builds a specific build configuration of this project. Does nothing if the project is closed + * or the build configuration does not exist. + *

                      + * Building a project involves executing the commands found + * in this project's build spec. The build is run for the specific project + * build configuration. + *

                      + *

                      + * This method may change resources; these changes will be reported + * in a subsequent resource change event. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * @param config build configuration to build + * @param kind the kind of build being requested. Valid values are: + *
                        + *
                      • IncrementalProjectBuilder.FULL_BUILD - indicates a full build.
                      • + *
                      • IncrementalProjectBuilder.INCREMENTAL_BUILD - indicates an incremental build.
                      • + *
                      • CLEAN_BUILD- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states. + *
                      + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic BUILD_FAILED + * code, but it could also be any other status code; it might + * also be a multi-status. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IProjectDescription + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IResourceRuleFactory#buildRule() + * @since 3.7 + */ + public void build(IBuildConfiguration config, int kind, IProgressMonitor monitor) throws CoreException; + + /** + * Closes this project. The project need not be open. Closing + * a closed project does nothing. + *

                      + * Closing a project involves ensuring that all important project-related + * state is safely stored on disk, and then discarding the in-memory + * representation of its resources and other volatile state, + * including session properties. + * After this method, the project continues to exist in the workspace + * but its member resources (and their members, etc.) do not. + * A closed project can later be re-opened. + *

                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event that includes + * an indication that this project has been closed and its members + * have been removed. + *

                      + *

                      + * 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 + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #open(IProgressMonitor) + * @see #isOpen() + * @see IResourceRuleFactory#modifyRule(IResource) + */ + public void close(IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new project resource in the workspace using the given project + * description. Upon successful completion, the project will exist but be closed. + *

                      + * Newly created projects have no session or persistent properties. + *

                      + *

                      + * If the project content area given in the project description does not + * contain a project description file, a project description file is written + * in the project content area with the natures, build spec, comment, and + * referenced projects as specified in the given project description. + * If there is an existing project description file, it is not overwritten. + * In either case, this method does not cause natures to be configured. + *

                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project has been added to the workspace. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param description the project description + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project already exists in the workspace.
                      • + *
                      • The name of this resource is not valid (according to + * IWorkspace.validateName).
                      • + *
                      • The project location is not valid (according to + * IWorkspace.validateProjectLocation).
                      • + *
                      • The project description file could not be created in the project + * content area.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#validateProjectLocation(IProject, IPath) + * @see IResourceRuleFactory#createRule(IResource) + */ + public void create(IProjectDescription description, IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new project resource in the workspace with files in the default + * location in the local file system. Upon successful completion, the project + * will exist but be closed. + *

                      + * Newly created projects have no session or persistent properties. + *

                      + *

                      + * If the project content area does not contain a project description file, + * an initial project description file is written in the project content area + * with the following information: + *

                        + *
                      • no references to other projects
                      • + *
                      • no natures
                      • + *
                      • an empty build spec
                      • + *
                      • an empty comment
                      • + *
                      + * If there is an existing project description file, it is not overwritten. + *

                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that this project has been added to the workspace. + *

                      + *

                      + * 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 + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project already exists in the workspace.
                      • + *
                      • The name of this resource is not valid (according to + * IWorkspace.validateName).
                      • + *
                      • The project location is not valid (according to + * IWorkspace.validateProjectLocation).
                      • + *
                      • The project description file could not be created in the project + * content area.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#validateProjectLocation(IProject, IPath) + * @see IResourceRuleFactory#createRule(IResource) + */ + public void create(IProgressMonitor monitor) throws CoreException; + + /** + * Creates a new project resource in the workspace using the given project + * description. Upon successful completion, the project will exist but be closed. + *

                      + * Newly created projects have no session or persistent properties. + *

                      + *

                      + * If the project content area given in the project description does not + * contain a project description file, a project description file is written + * in the project content area with the natures, build spec, comment, and + * referenced projects as specified in the given project description. + * If there is an existing project description file, it is not overwritten. + * In either case, this method does not cause natures to be configured. + *

                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project has been added to the workspace. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + *

                      + * The {@link IResource#HIDDEN} update flag indicates that this resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link IResource#setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

                      + *

                      + * Update flags other than those listed above are ignored. + *

                      + * + * @param description the project description + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project already exists in the workspace.
                      • + *
                      • The name of this resource is not valid (according to + * IWorkspace.validateName).
                      • + *
                      • The project location is not valid (according to + * IWorkspace.validateProjectLocation).
                      • + *
                      • The project description file could not be created in the project + * content area.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#validateProjectLocation(IProject, IPath) + * @see IResourceRuleFactory#createRule(IResource) + * + * @since 3.4 + */ + public void create(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this project from the workspace. + * No action is taken if this project does not exist. + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   delete(
                      +	 *     (deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT )
                      +	 *        | (force ? FORCE : IResource.NONE),
                      +	 *     monitor);
                      +	 * 
                      + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param deleteContent a flag controlling how whether content is + * aggressively deleted + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project could not be deleted.
                      • + *
                      • This project's contents could not be deleted.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int, IProgressMonitor) + * @see #open(IProgressMonitor) + * @see #close(IProgressMonitor) + * @see IResource#delete(int,IProgressMonitor) + * @see IResourceRuleFactory#deleteRule(IResource) + */ + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the active build configuration for the project. + *

                      + * If at any point the active configuration is removed from the project, for example + * when updating the list of build configurations, the active build configuration will be set to + * the first build configuration specified by {@link IProjectDescription#setBuildConfigs(String[])}. + *

                      + * If all of the build configurations are removed, the active build configuration will be set to the + * default configuration. + *

                      + * @return the active build configuration + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist.
                      • + *
                      • This project is not open.
                      • + *
                      + * @since 3.7 + */ + public IBuildConfiguration getActiveBuildConfig() throws CoreException; + + /** + * Returns the project {@link IBuildConfiguration} with the given name for this project. + * @param configName the name of the configuration to get + * @return a project configuration + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist.
                      • + *
                      • This project is not open.
                      • + *
                      • The configuration does not exist in this project.
                      • + *
                      + * @see #getBuildConfigs() + * @since 3.7 + */ + public IBuildConfiguration getBuildConfig(String configName) throws CoreException; + + /** + * Returns the build configurations for this project. A project always has at + * least one build configuration, so this will never return an empty list or null. + * The result will not contain duplicates. + * @return a list of project build configurations + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist.
                      • + *
                      • This project is not open.
                      • + *
                      + * @since 3.7 + */ + public IBuildConfiguration[] getBuildConfigs() throws CoreException; + + /** + * Returns this project's content type matcher. This content type matcher takes + * project specific preferences and nature-content type associations into + * account. + * + * @return the content type matcher for this project + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist.
                      • + *
                      • This project is not open.
                      • + *
                      + * @see IContentTypeMatcher + * @since 3.1 + */ + public IContentTypeMatcher getContentTypeMatcher() throws CoreException; + + /** + * Returns the description for this project. + * The returned value is a copy and cannot be used to modify + * this project. The returned value is suitable for use in creating, + * copying and moving other projects. + * + * @return the description for this project + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist.
                      • + *
                      • This project is not open.
                      • + *
                      + * @see #create(IProgressMonitor) + * @see #create(IProjectDescription, IProgressMonitor) + * @see IResource#copy(IProjectDescription, int, IProgressMonitor) + * @see #move(IProjectDescription, boolean, IProgressMonitor) + */ + public IProjectDescription getDescription() throws CoreException; + + /** + * Returns a handle to the file with the given name in this project. + *

                      + * This is a resource handle operation; neither the resource nor + * the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

                      + * + * @param name the string name of the member file + * @return the (handle of the) member file + * @see #getFolder(String) + */ + public IFile getFile(String name); + + /** + * Returns a handle to the folder with the given name in this project. + *

                      + * This is a resource handle operation; neither the container + * nor the result need exist in the workspace. + * The validation check on the resource name/path is not done + * when the resource handle is constructed; rather, it is done + * automatically as the resource is created. + *

                      + * + * @param name the string name of the member folder + * @return the (handle of the) member folder + * @see #getFile(String) + */ + public IFolder getFolder(String name); + + /** + * Returns the specified project nature for this project or null if + * the project nature has not been added to this project. + * Clients may downcast to a more concrete type for more nature-specific methods. + * The documentation for a project nature specifies any such additional protocol. + *

                      + * This may cause the plug-in that provides the given nature to be activated. + *

                      + * + * @param natureId the fully qualified nature extension identifier, formed + * by combining the nature extension id with the id of the declaring plug-in. + * (e.g. "com.example.acmeplugin.coolnature") + * @return the project nature object + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist.
                      • + *
                      • This project is not open.
                      • + *
                      • The project nature extension could not be found.
                      • + *
                      + */ + public IProjectNature getNature(String natureId) throws CoreException; + + /** + * Returns the location in the local file system of the project-specific + * working data area for use by the given plug-in or null + * if the project does not exist. + *

                      + * The content, structure, and management of this area is + * the responsibility of the plug-in. This area is deleted when the + * project is deleted. + *

                      + * This project needs to exist but does not need to be open. + *

                      + * @param plugin the plug-in + * @return a local file system path + * @deprecated Use IProject.getWorkingLocation(plugin.getUniqueIdentifier()). + */ + @Deprecated + public IPath getPluginWorkingLocation(IPluginDescriptor plugin); + + /** + * Returns the location in the local file system of the project-specific + * working data area for use by the bundle/plug-in with the given identifier, + * or null if the project does not exist. + *

                      + * The content, structure, and management of this area is + * the responsibility of the bundle/plug-in. This area is deleted when the + * project is deleted. + *

                      + * This project needs to exist but does not need to be open. + *

                      + * @param id the bundle or plug-in's identifier + * @return a local file system path + * @since 3.0 + */ + public IPath getWorkingLocation(String id); + + /** + * Returns the projects referenced by this project. This includes + * both the static and dynamic references of this project. + * The returned projects need not exist in the workspace. + * The result will not contain duplicates. Returns an empty + * array if there are no referenced projects. + * + * @return a list of projects + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist.
                      • + *
                      • This project is not open.
                      • + *
                      + * @see #getReferencedBuildConfigs(String, boolean) + * @see IProjectDescription#getReferencedProjects() + * @see IProjectDescription#getDynamicReferences() + */ + public IProject[] getReferencedProjects() throws CoreException; + + /** + * Clears the cache of dynamic project references for this project. Invoking this + * method will cause the dynamic project references to be recomputed the next time + * they are accessed (for example, in a call to {@link #getReferencedProjects()}. + * It is not necessary to hold the workspace lock when invoking this method. Plugins + * that provide an {@link IDynamicReferenceProvider} should invoke this method to + * inform the rest of the application when one or more dynamic project references + * may have changed. This will also clear any other cached data that is derived from + * the dynamic references. + * + * @since 3.12 + */ + public void clearCachedDynamicReferences(); + + /** + * Returns the list of all open projects which reference + * this project. This project may or may not exist. Returns + * an empty array if there are no referencing projects. + * + * @return a list of open projects referencing this project + */ + public IProject[] getReferencingProjects(); + + /** + * Returns the build configurations referenced by the passed in build configuration + * on this project. + *

                      + * This includes both the static and dynamic project level references. These are + * converted to build configurations pointing at the currently active referenced + * project configuration. + * The result will not contain duplicates. + *

                      + *

                      + * References to active configurations will be translated to references to actual + * build configurations, if the project is accessible. Note that if includeMissing + * is true BuildConfigurations which can't be resolved (i.e. exist on missing projects, + * or aren't listed on the referenced project) are still included in the returned + * IBuildConfiguration array. + *

                      + *

                      + * Returns an empty array if there are no references. + *

                      + * + * @param configName the configuration to get the references for + * @param includeMissing boolean controls whether unresolved buildConfiguration should + * be included in the result + * @return an array of project build configurations + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist.
                      • + *
                      • This project is not open.
                      • + *
                      • The build configuration does not exist in this project.
                      • + *
                      + * @see IProjectDescription#getBuildConfigReferences(String) + * @since 3.7 + */ + public IBuildConfiguration[] getReferencedBuildConfigs(String configName, boolean includeMissing) throws CoreException; + + /** + * Checks whether the project has the specified build configuration. + * + * @param configName the configuration + * @return true if the project has the specified configuration, false otherwise + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist.
                      • + *
                      • This project is not open.
                      • + *
                      + * @since 3.7 + */ + public boolean hasBuildConfig(String configName) throws CoreException; + + /** + * Returns whether the project nature specified by the given + * nature extension id has been added to this project. + * + * @param natureId the nature extension identifier + * @return true if the project has the given nature + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist.
                      • + *
                      • This project is not open.
                      • + *
                      + */ + public boolean hasNature(String natureId) throws CoreException; + + /** + * Returns true if the project nature specified by the given + * nature extension id is enabled for this project, and false otherwise. + *

                      + *

                        Reasons for a nature not to be enabled include: + *
                      • The nature is not available in the install.
                      • + *
                      • The nature has not been added to this project.
                      • + *
                      • The nature has a prerequisite that is not enabled + * for this project.
                      • + *
                      • The nature specifies "one-of-nature" membership in + * a set, and there is another nature on this project belonging + * to that set.
                      • + *
                      • The prerequisites for the nature form a cycle.
                      • + *
                      + *

                      + * @param natureId the nature extension identifier + * @return true if the given nature is enabled for this project + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist.
                      • + *
                      • This project is not open.
                      • + *
                      + * @since 2.0 + * @see IWorkspace#validateNatureSet(String[]) + */ + public boolean isNatureEnabled(String natureId) throws CoreException; + + /** + * Returns whether this project is open. + *

                      + * A project must be opened before it can be manipulated. + * A closed project is passive and has a minimal memory + * footprint; a closed project has no members. + *

                      + * + * @return true if this project is open, false if + * this project is closed or does not exist + * @see #open(IProgressMonitor) + * @see #close(IProgressMonitor) + */ + public boolean isOpen(); + + /** + * Loads a snapshot of project meta-data from the given location URI. + * Must be called after the project has been created, but before it is + * opened. The options constant controls what kind of snapshot information + * to load. Valid option values include:
                        + *
                      • {@link IProject#SNAPSHOT_TREE} - load resource tree (refresh info) + *
                      + * + * @param options kind of snapshot information to load + * @param snapshotLocation URI to load from + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • The snapshot was not found at the specified URI.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * @see #saveSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public void loadSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException; + + /** + * Renames this project so that it is located at the name in + * the given description. + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   move(description, (force ? FORCE : IResource.NONE), monitor);
                      +	 * 
                      + *

                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource has been removed from its parent + * and that a corresponding resource has been added to its new parent. + * Additional information provided with resource delta shows that these + * additions and removals are related. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param description the description for the destination project + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                        + *
                      • This resource is not accessible.
                      • + *
                      • This resource or one of its descendents is not local.
                      • + *
                      • This resource or one of its descendents is out of sync with the local file system + * and force is false.
                      • + *
                      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + * @see IResource#move(IProjectDescription,int,IProgressMonitor) + * @see IResourceRuleFactory#moveRule(IResource, IResource) + */ + public void move(IProjectDescription description, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Opens this project. No action is taken if the project is already open. + *

                      + * Opening a project constructs an in-memory representation + * of its resources from information stored on disk. + *

                      + *

                      + * When a project is opened for the first time, initial information about the + * project's existing resources can be obtained in the following ways: + *

                        + *
                      • If a {@link #loadSnapshot(int, URI, IProgressMonitor)} call has been made + * before the open, resources are restored from that file (a file written by + * {@link #saveSnapshot(int, URI, IProgressMonitor)}). When the snapshot includes + * resource tree information and can be loaded without error, no refresh is initiated, + * so the project's resource tree will match what the snapshot provides. + *
                      • Otherwise, when the {@link IResource#BACKGROUND_REFRESH} flag is specified, + * resources on disk will be added to the project in the background after + * this method returns. Child resources of the project may not be available + * until this background refresh completes. + *
                      • Otherwise, resource information is obtained with a refresh operation in the + * foreground, before this method returns. + *
                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event that includes + * an indication that the project has been opened and its resources + * have been added to the tree. If the BACKGROUND_REFRESH + * update flag is specified, multiple resource change events may occur as + * resources on disk are discovered and added to the tree. + *

                      + *

                      + * 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 + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #close(IProgressMonitor) + * @see IResource#BACKGROUND_REFRESH + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 3.1 + */ + public void open(int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Opens this project. No action is taken if the project is already open. + *

                      + * This is a convenience method, fully equivalent to + * open(IResource.NONE, monitor). + *

                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event that includes + * an indication that the project has been opened and its resources + * have been added to the tree. + *

                      + *

                      + * 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 + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #close(IProgressMonitor) + * @see IResourceRuleFactory#modifyRule(IResource) + */ + public void open(IProgressMonitor monitor) throws CoreException; + + /** + * Writes a snapshot of project meta-data into the given location URI. + * The options constant controls what kind of snapshot information to + * write. Valid option values include:
                        + *
                      • {@link IProject#SNAPSHOT_TREE} - save resource tree (refresh info) + *
                      + * + * @param options kind of snapshot information to save + * @param snapshotLocation URI for saving the snapshot to + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • The URI is not writable or an error occurs writing the data.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * @see #loadSnapshot(int, URI, IProgressMonitor) + * @since 3.6 + */ + public void saveSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException; + + /** + * Changes this project resource to match the given project + * description. This project should exist and be open. + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   setDescription(description, KEEP_HISTORY, monitor);
                      +	 * 
                      + *

                      + *

                      + * This method requires the {@link IWorkspaceRoot} scheduling rule. + *

                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project's content has changed. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param description the project description + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist in the workspace.
                      • + *
                      • This project is not open.
                      • + *
                      • The location in the local file system corresponding to the project + * description file is occupied by a directory.
                      • + *
                      • The workspace is out of sync with the project description file + * in the local file system .
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      • The file modification validator disallowed the change.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see #getDescription() + * @see IProjectNature#configure() + * @see IProjectNature#deconfigure() + * @see #setDescription(IProjectDescription,int,IProgressMonitor) + */ + public void setDescription(IProjectDescription description, IProgressMonitor monitor) throws CoreException; + + /** + * Changes this project resource to match the given project + * description. This project should exist and be open. + *

                      + * The given project description is used to change the project's + * natures, build spec, comment, and referenced projects. + * The name and location of a project cannot be changed using this method; + * these settings in the project description are ignored. To change a project's + * name or location, use {@link IResource#move(IProjectDescription, int, IProgressMonitor)}. + * The project's session and persistent properties are not affected. + *

                      + *

                      + * If the new description includes nature ids of natures that the project + * did not have before, these natures will be configured in automatically, + * which involves instantiating the project nature and calling + * {@link IProjectNature#configure()} on it. An internal reference to the + * nature object is retained, and will be returned on subsequent calls to + * getNature for the specified nature id. Similarly, any natures + * the project had which are no longer required will be automatically + * de-configured by calling {@link IProjectNature#deconfigure} + * on the nature object and letting go of the internal reference to it. + *

                      + *

                      + * The FORCE update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If FORCE is not specified, the method will only attempt + * to overwrite the project's description file in the local file system + * provided it is in sync with the workspace. This option ensures there is no + * unintended data loss; it is the recommended setting. + * However, if FORCE is specified, an attempt will be made + * to write the project description file in the local file system, overwriting + * any existing one if need be. + *

                      + *

                      + * The KEEP_HISTORY update flag controls whether or not a copy of + * current contents of the project description file should be captured in the + * workspace's local history. The local history mechanism serves as a safety net + * to help the user recover from mistakes that might otherwise result in data + * loss. Specifying KEEP_HISTORY is recommended. Note that local + * history is maintained with each individual project, and gets discarded when + * a project is deleted from the workspace. + *

                      + *

                      + * The AVOID_NATURE_CONFIG update flag controls whether or + * not added and removed natures should be configured or de-configured. If this + * flag is not specified, then added natures will be configured and removed natures + * will be de-configured. If this flag is specified, natures can still be added or + * removed, but they will not be configured or de-configured. + *

                      + *

                      + * The scheduling rule required for this operation depends on the + * AVOID_NATURE_CONFIG flag. If the flag is specified the + * {@link IResourceRuleFactory#modifyRule} is required; If the flag is not specified, + * the {@link IWorkspaceRoot} scheduling rule is required. + *

                      + *

                      + * Update flags other than FORCE, KEEP_HISTORY, + * and AVOID_NATURE_CONFIG are ignored. + *

                      + *

                      + * Prior to modifying the project description file, the file modification + * validator (if provided by the Team plug-in), will be given a chance to + * perform any last minute preparations. Validation is performed by calling + * IFileModificationValidator.validateSave on the project + * description file. If the validation fails, then this operation will fail. + *

                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event, including an indication + * that the project's content has changed. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param description the project description + * @param updateFlags bit-wise or of update flag constants + * (FORCE, KEEP_HISTORY and + * AVOID_NATURE_CONFIG) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This project does not exist in the workspace.
                      • + *
                      • This project is not open.
                      • + *
                      • The location in the local file system corresponding to the project + * description file is occupied by a directory.
                      • + *
                      • The workspace is not in sync with the project + * description file in the local file system and FORCE is not + * specified.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      • The file modification validator disallowed the change.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see #getDescription() + * @see IProjectNature#configure() + * @see IProjectNature#deconfigure() + * @see IResource#FORCE + * @see IResource#KEEP_HISTORY + * @see IResource#AVOID_NATURE_CONFIG + * @see IResourceRuleFactory#modifyRule(IResource) + * @since 2.0 + */ + public void setDescription(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java new file mode 100644 index 0000000000..dbc45167f3 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectDescription.java @@ -0,0 +1,387 @@ +/******************************************************************************* + * 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 + * Broadcom Corporation - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A project description contains the meta-data required to define + * a project. In effect, a project description is a project's "content". + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IProjectDescription { + /** + * Constant that denotes the name of the project description file (value + * ".project"). + * The handle of a project's description file is + * project.getFile(DESCRIPTION_FILE_NAME). + * The project description file is located in the root of the project's content area. + * + * @since 2.0 + */ + public static final String DESCRIPTION_FILE_NAME = ".project"; //$NON-NLS-1$ + + /** + * Returns the build configurations referenced by the specified configuration for the + * described project. + *

                      + * These references are persisted by the workspace in a private location outside the + * project description file, and as such will not be shared when a project is exported + * or persisted in a repository. As such clients are always + * responsible for setting these references when a project is created or recreated. + *

                      + *

                      + * The referenced build configurations need not exist in the workspace. + * The result will not contain duplicates. The order of the references is preserved + * from the call to {@link #setBuildConfigReferences(String, IBuildConfiguration[])}. + * Returns an empty array if the provided config doesn't dynamically reference + * any other build configurations, or the given config does not exist in this description. + *

                      + * @param configName the configuration in the described project to get the references for + * @return a list of dynamic build configurations + * @see #setBuildConfigReferences(String, IBuildConfiguration[]) + * @since 3.7 + */ + public IBuildConfiguration[] getBuildConfigReferences(String configName); + + /** + * Returns the list of build commands to run when building the described project. + * The commands are listed in the order in which they are to be run. + * + * @return the list of build commands for the described project + */ + public ICommand[] getBuildSpec(); + + /** + * Returns the descriptive comment for the described project. + * + * @return the comment for the described project + */ + public String getComment(); + + /** + * Returns the dynamic project references for the described project. Dynamic + * project references can be used instead of simple project references in cases + * where the reference information is computed dynamically be a third party. + * These references are persisted by the workspace in a private location outside + * the project description file, and as such will not be shared when a project is + * exported or persisted in a repository. A client using project references + * is always responsible for setting these references when a project is created + * or recreated. + *

                      + * The returned projects need not exist in the workspace. The result will not + * contain duplicates. Returns an empty array if there are no dynamic project + * references on this description. + * + * @see #getBuildConfigReferences(String) + * @see #getReferencedProjects() + * @see #setDynamicReferences(IProject[]) + * @return a list of projects + * @since 3.0 + */ + public IProject[] getDynamicReferences(); + + /** + * Returns the local file system location for the described project. The path + * will be either an absolute file system path, or a relative path whose first + * segment is the name of a workspace path variable. null is + * returned if the default location should be used. This method will return + * null if this project is not located in the local file system. + * + * @return the location for the described project or null + * @deprecated Since 3.2, project locations are not necessarily in the local file + * system. The more general {@link #getLocationURI()} method should be used instead. + */ + @Deprecated + public IPath getLocation(); + + /** + * Returns the location URI for the described project. null is + * returned if the default location should be used. + * + * @return the location for the described project or null + * @since 3.2 + * @see #setLocationURI(URI) + */ + public URI getLocationURI(); + + /** + * Returns the name of the described project. + * + * @return the name of the described project + */ + public String getName(); + + /** + * Returns the list of natures associated with the described project. + * Returns an empty array if there are no natures on this description. + * + * @return the list of natures for the described project + * @see #setNatureIds(String[]) + */ + public String[] getNatureIds(); + + /** + * Returns the projects referenced by the described project. These references + * are persisted in the project description file (".project") and as such + * will be shared whenever the project is exported to another workspace. For + * references that are likely to change from one workspace to another, dynamic + * references should be used instead. + *

                      + * The projects need not exist in the workspace. + * The result will not contain duplicates. Returns an empty + * array if there are no referenced projects on this description. + * + * @see #getDynamicReferences() + * @see #getBuildConfigReferences(String) + * @return a list of projects + */ + public IProject[] getReferencedProjects(); + + /** + * Returns whether the project nature specified by the given + * nature extension id has been added to the described project. + * + * @param natureId the nature extension identifier + * @return true if the described project has the given nature + */ + public boolean hasNature(String natureId); + + /** + * Returns a new build command. + *

                      + * Note that the new command does not become part of this project + * description's build spec until it is installed via the setBuildSpec + * method. + *

                      + * + * @return a new command + * @see #setBuildSpec(ICommand[]) + */ + public ICommand newCommand(); + + /** + * Sets the active configuration for the described project. + *

                      + * If a configuration with the specified name does not exist in the project then the + * first configuration in the project is treated as the active configuration. + *

                      + * + * @param configName the configuration to set as the active or default + * @since 3.7 + */ + public void setActiveBuildConfig(String configName); + + /** + * Sets the build configurations for the described project. + *

                      + * The passed in names must all be non-null. + * Before they are set, duplicates are removed. + *

                      + *

                      + * All projects have one default build configuration, and it is impossible to configure + * the project to have no build configurations. + * If the input is null or an empty list, the current configurations are removed, + * and a default build configuration is (re-)added. + *

                      + *

                      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

                      + * + * @param configNames the configurations to set for the described project + * @see IProject#getActiveBuildConfig() + * @see IProject#getBuildConfigs() + * @see IProjectDescription#setActiveBuildConfig(String) + * @since 3.7 + */ + public void setBuildConfigs(String[] configNames); + + /** + * Sets the build configurations referenced by the specified configuration. + *

                      + * The configuration to which references are being added needs to exist in this + * description, but the referenced projects and build configurations need not exist. + * A reference with null configuration name is resolved to the active build configuration + * on use. + * Duplicates will be removed. The order of the referenced build configurations is preserved. + * If the given configuration does not exist in this description then this has no effect. + *

                      + *

                      + * References at the build configuration level take precedence over references at the project level. + *

                      + *

                      + * Like dynamic references, these build configuration references are persisted as part of workspace + * metadata. + *

                      + *

                      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

                      + * + * @see #getBuildConfigReferences(String) + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @param configName the configuration in the described project to set the references for + * @param references list of build configuration references + * @since 3.7 + */ + public void setBuildConfigReferences(String configName, IBuildConfiguration[] references); + + /** + * Sets the list of build command to run when building the described project. + *

                      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

                      + * + * @param buildSpec the array of build commands to run + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getBuildSpec() + * @see #newCommand() + */ + public void setBuildSpec(ICommand[] buildSpec); + + /** + * Sets the comment for the described project. + *

                      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

                      + * + * @param comment the comment for the described project + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getComment() + */ + public void setComment(String comment); + + /** + * Sets the dynamic project references for the described project. + * The projects need not exist in the workspace. Duplicates will be + * removed. + *

                      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

                      + * @deprecated please use {@link IDynamicReferenceProvider} with the builders extension point to supply dynamic references + * @see #getDynamicReferences() + * @see #setBuildConfigReferences(String, IBuildConfiguration[]) + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @param projects list of projects + * @since 3.0 + */ + @Deprecated + public void setDynamicReferences(IProject[] projects); + + /** + * Sets the local file system location for the described project. The path must + * be either an absolute file system path, or a relative path whose first + * segment is the name of a defined workspace path variable. If + * null is specified, the default location is used. + *

                      + * Setting the location on a description for a project which already + * exists has no effect; the new project location is ignored when the + * description is set on the already existing project. This method is + * intended for use on descriptions for new projects or for destination + * projects for copy and move. + *

                      + *

                      + * This operation maps the root folder of the project to the exact location + * provided. For example, if the location for project named "P" is set + * to the path c:\my_plugins\Project1, the file resource at workspace path + * /P/index.html would be stored in the local file system at + * c:\my_plugins\Project1\index.html. + *

                      + * + * @param location the location for the described project or null + * @see #getLocation() + */ + public void setLocation(IPath location); + + /** + * Sets the location for the described project. + * If null is specified, the default location is used. + *

                      + * Setting the location on a description for a project which already + * exists has no effect; the new project location is ignored when the + * description is set on the already existing project. This method is + * intended for use on descriptions for new projects or for destination + * projects for copy and move. + *

                      + *

                      + * This operation maps the root folder of the project to the exact location + * provided. For example, if the location for project named "P" is set + * to the URI file://c:/my_plugins/Project1, the file resource at workspace path + * /P/index.html would be stored in the local file system at + * file://c:/my_plugins/Project1/index.html. + *

                      + * + * @param location the location for the described project or null + * @see #getLocationURI() + * @see IWorkspace#validateProjectLocationURI(IProject, URI) + * @since 3.2 + */ + public void setLocationURI(URI location); + + /** + * Sets the name of the described project. + *

                      + * Setting the name on a description and then setting the + * description on the project has no effect; the new name is ignored. + *

                      + *

                      + * Creating a new project with a description name which doesn't + * match the project handle name results in the description name + * being ignored; the project will be created using the name + * in the handle. + *

                      + * + * @param projectName the name of the described project + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getName() + */ + public void setName(String projectName); + + /** + * Sets the list of natures associated with the described project. + * A project created with this description will have these natures + * added to it in the given order. + *

                      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

                      + * + * @param natures the list of natures + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #getNatureIds() + */ + public void setNatureIds(String[] natures); + + /** + * Sets the referenced projects, ignoring any duplicates. + * The order of projects is preserved. + * The projects need not exist in the workspace. + *

                      + * Users must call {@link IProject#setDescription(IProjectDescription, int, IProgressMonitor)} + * before changes made to this description take effect. + *

                      + * + * @param projects a list of projects + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @see #setBuildConfigReferences(String, IBuildConfiguration[]) + * @see #getReferencedProjects() + */ + public void setReferencedProjects(IProject[] projects); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java new file mode 100644 index 0000000000..aa649adb24 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * Interface for project nature runtime classes. + * It can configure a project with the project nature, or de-configure it. + * When a project is configured with a project nature, this is + * recorded in the list of project natures on the project. + * Individual project natures may expose a more specific runtime type, + * with additional API for manipulating the project in a + * nature-specific way. + *

                      + * Clients may implement this interface. + *

                      + * + * @see IProject#getNature(String) + * @see IProject#hasNature(String) + * @see IProjectDescription#getNatureIds() + * @see IProjectDescription#hasNature(String) + * @see IProjectDescription#setNatureIds(String[]) + */ +public interface IProjectNature { + /** + * Configures this nature for its project. This is called by the workspace + * when natures are added to the project using IProject.setDescription + * and should not be called directly by clients. The nature extension + * id is added to the list of natures before this method is called, + * and need not be added here. + * + * Exceptions thrown by this method will be propagated back to the caller + * of IProject.setDescription, but the nature will remain in + * the project description. + * + * @exception CoreException if this method fails. + */ + public void configure() throws CoreException; + + /** + * De-configures this nature for its project. This is called by the workspace + * when natures are removed from the project using + * IProject.setDescription and should not be called directly by + * clients. The nature extension id is removed from the list of natures before + * this method is called, and need not be removed here. + * + * Exceptions thrown by this method will be propagated back to the caller + * of IProject.setDescription, but the nature will still be + * removed from the project description. + * * + * @exception CoreException if this method fails. + */ + public void deconfigure() throws CoreException; + + /** + * Returns the project to which this project nature applies. + * + * @return the project handle + */ + public IProject getProject(); + + /** + * Sets the project to which this nature applies. + * Used when instantiating this project nature runtime. + * This is called by IProject.create() or + * IProject.setDescription() + * and should not be called directly by clients. + * + * @param project the project to which this nature applies + */ + public void setProject(IProject project); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java new file mode 100644 index 0000000000..cdd84de7eb --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +/** + * A project nature descriptor contains information about a project nature + * obtained from the plug-in manifest (plugin.xml) file. + *

                      + * Nature descriptors are platform-defined objects that exist + * independent of whether that nature's plug-in has been started. + * In contrast, a project nature's runtime object (IProjectNature) + * generally runs plug-in-defined code. + *

                      + * + * @see IProjectNature + * @see IWorkspace#getNatureDescriptor(String) + * @since 2.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 IProjectNatureDescriptor { + /** + * Returns the unique identifier of this nature. + *

                      + * The nature identifier is composed of the nature's plug-in id and the simple + * id of the nature extension. For example, if plug-in "com.xyz" + * defines a nature extension with id "myNature", the unique + * nature identifier will be "com.xyz.myNature". + *

                      + * @return the unique nature identifier + */ + public String getNatureId(); + + /** + * Returns a displayable label for this nature. + * Returns the empty string if no label for this nature + * is specified in the plug-in manifest file. + *

                      Note that any translation specified in the plug-in manifest + * file is automatically applied. + *

                      + * + * @return a displayable string label for this nature, + * possibly the empty string + */ + public String getLabel(); + + /** + * Returns the unique identifiers of the natures required by this nature. + * Nature requirements are specified by the "requires-nature" + * element on a nature extension. + * Returns an empty array if no natures are required by this nature. + * + * @return an array of nature ids that this nature requires, + * possibly an empty array. + */ + public String[] getRequiredNatureIds(); + + /** + * Returns the identifiers of the nature sets that this nature belongs to. + * Nature set inclusion is specified by the "one-of-nature" + * element on a nature extension. + * Returns an empty array if no nature sets are specified for this nature. + * + * @return an array of nature set ids that this nature belongs to, + * possibly an empty array. + */ + public String[] getNatureSetIds(); + + /** + * Returns whether this project nature allows linked resources to be created + * in projects where this nature is installed. + * + * @return boolean true if creating links is allowed, + * and false otherwise. + * @see IFolder#createLink(org.eclipse.core.runtime.IPath, int, org.eclipse.core.runtime.IProgressMonitor) + * @see IFile#createLink(org.eclipse.core.runtime.IPath, int, org.eclipse.core.runtime.IProgressMonitor) + * @since 2.1 + */ + public boolean isLinkingAllowed(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java new file mode 100644 index 0000000000..738b3c01bd --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java @@ -0,0 +1,2790 @@ +/******************************************************************************* + * 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 + * Red Hat Incorporated - get/setResourceAttribute code + * Oakland Software Incorporated - added getSessionProperties and getPersistentProperties + * Serge Beauchamp (Freescale Semiconductor) - [252996] add hasFilters() + * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.net.URI; +import java.util.Map; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * The workspace analog of file system files + * and directories. There are exactly four types of resource: + * files, folders, projects and the workspace root. + *

                      + * File resources are similar to files in that they + * hold data directly. Folder resources are analogous to directories in that they + * hold other resources but cannot directly hold data. Project resources + * group files and folders into reusable clusters. The workspace root is the + * top level resource under which all others reside. + *

                      + *

                      + * Features of resources: + *

                        + *
                      • IResource objects are handles to state maintained + * by a workspace. That is, resource objects do not actually contain data + * themselves but rather represent resource state and give it behavior. Programmers + * are free to manipulate handles for resources that do not exist in a workspace + * but must keep in mind that some methods and operations require that an actual + * resource be available.
                      • + *
                      • Resources have two different kinds of properties as detailed below. All + * properties are keyed by qualified names.
                      • + *
                          + *
                        • Session properties: Session properties live for the lifetime of one execution of + * the workspace. They are not stored on disk. They can carry arbitrary + * object values. Clients should be aware that these values are kept in memory + * at all times and, as such, the values should not be large.
                        • + *
                        • Persistent properties: Persistent properties have string values which are stored + * on disk across platform sessions. The value of a persistent property is a + * string which should be short (i.e., under 2KB).
                        • + *
                        + * + *
                      • Resources are identified by type and by their path, which is similar to a file system + * path. The name of a resource is the last segment of its path. A resource's parent + * is located by removing the last segment (the resource's name) from the resource's full path.
                      • + *
                      • Resources can be local or non-local. A non-local resource is one whose + * contents and properties have not been fetched from a repository.
                      • + *
                      • Phantom resources represent incoming additions or outgoing deletions + * which have yet to be reconciled with a synchronization partner.
                      • + *
                      + *

                      + *

                      + * Resources implement the {@link IAdaptable} interface; + * extensions are managed by the platform's adapter manager. + *

                      + * + * @see IWorkspace + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResource extends IAdaptable, ISchedulingRule { + + /*==================================================================== + * Constants defining resource types: There are four possible resource types + * and their type constants are in the integer range 1 to 8 as defined below. + *====================================================================*/ + + /** + * Type constant (bit mask value 1) which identifies file resources. + * + * @see IResource#getType() + * @see IFile + */ + public static final int FILE = 0x1; + + /** + * Type constant (bit mask value 2) which identifies folder resources. + * + * @see IResource#getType() + * @see IFolder + */ + public static final int FOLDER = 0x2; + + /** + * Type constant (bit mask value 4) which identifies project resources. + * + * @see IResource#getType() + * @see IProject + */ + public static final int PROJECT = 0x4; + + /** + * Type constant (bit mask value 8) which identifies the root resource. + * + * @see IResource#getType() + * @see IWorkspaceRoot + */ + public static final int ROOT = 0x8; + + /*==================================================================== + * Constants defining the depth of resource tree traversal: + *====================================================================*/ + + /** + * Depth constant (value 0) indicating this resource, but not any of its members. + */ + public static final int DEPTH_ZERO = 0; + + /** + * Depth constant (value 1) indicating this resource and its direct members. + */ + public static final int DEPTH_ONE = 1; + + /** + * Depth constant (value 2) indicating this resource and its direct and + * indirect members at any depth. + */ + public static final int DEPTH_INFINITE = 2; + + /*==================================================================== + * Constants for update flags for delete, move, copy, open, etc.: + *====================================================================*/ + + /** + * Update flag constant (bit mask value 1) indicating that the operation + * should proceed even if the resource is out of sync with the local file + * system. + * + * @since 2.0 + */ + public static final int FORCE = 0x1; + + /** + * Update flag constant (bit mask value 2) indicating that the operation + * should maintain local history by taking snapshots of the contents of + * files just before being overwritten or deleted. + * + * @see IFile#getHistory(IProgressMonitor) + * @since 2.0 + */ + public static final int KEEP_HISTORY = 0x2; + + /** + * Update flag constant (bit mask value 4) indicating that the operation + * should delete the files and folders of a project. + *

                      + * Deleting a project that is open ordinarily deletes all its files and folders, + * whereas deleting a project that is closed retains its files and folders. + * Specifying ALWAYS_DELETE_PROJECT_CONTENT indicates that the contents + * of a project are to be deleted regardless of whether the project is open or closed + * at the time; specifying NEVER_DELETE_PROJECT_CONTENT indicates that + * the contents of a project are to be retained regardless of whether the project + * is open or closed at the time. + *

                      + * + * @see #NEVER_DELETE_PROJECT_CONTENT + * @since 2.0 + */ + public static final int ALWAYS_DELETE_PROJECT_CONTENT = 0x4; + + /** + * Update flag constant (bit mask value 8) indicating that the operation + * should preserve the files and folders of a project. + *

                      + * Deleting a project that is open ordinarily deletes all its files and folders, + * whereas deleting a project that is closed retains its files and folders. + * Specifying ALWAYS_DELETE_PROJECT_CONTENT indicates that the contents + * of a project are to be deleted regardless of whether the project is open or closed + * at the time; specifying NEVER_DELETE_PROJECT_CONTENT indicates that + * the contents of a project are to be retained regardless of whether the project + * is open or closed at the time. + *

                      + * + * @see #ALWAYS_DELETE_PROJECT_CONTENT + * @since 2.0 + */ + public static final int NEVER_DELETE_PROJECT_CONTENT = 0x8; + + /** + * Update flag constant (bit mask value 16) indicating that the link creation + * should proceed even if the local file system file or directory is missing. + * + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @since 2.1 + */ + public static final int ALLOW_MISSING_LOCAL = 0x10; + + /** + * Update flag constant (bit mask value 32) indicating that a copy or move + * operation should only copy the link, rather than copy the underlying + * contents of the linked resource. + * + * @see #copy(IPath, int, IProgressMonitor) + * @see #move(IPath, int, IProgressMonitor) + * @since 2.1 + */ + public static final int SHALLOW = 0x20; + + /** + * Update flag constant (bit mask value 64) indicating that setting the + * project description should not attempt to configure and de-configure + * natures. + * + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @since 3.0 + */ + public static final int AVOID_NATURE_CONFIG = 0x40; + + /** + * Update flag constant (bit mask value 128) indicating that opening a + * project for the first time or creating a linked folder should refresh in the + * background. + * + * @see IProject#open(int, IProgressMonitor) + * @see IFolder#createLink(URI, int, IProgressMonitor) + * @since 3.1 + */ + public static final int BACKGROUND_REFRESH = 0x80; + + /** + * Update flag constant (bit mask value 256) indicating that a + * resource should be replaced with a resource of the same name + * at a different file system location. + * + * @see IFile#createLink(URI, int, IProgressMonitor) + * @see IFolder#createLink(URI, int, IProgressMonitor) + * @see IResource#move(IProjectDescription, int, IProgressMonitor) + * @since 3.2 + */ + public static final int REPLACE = 0x100; + + /** + * Update flag constant (bit mask value 512) indicating that ancestor + * resources of the target resource should be checked. + * + * @see IResource#isLinked(int) + * @since 3.2 + */ + public static final int CHECK_ANCESTORS = 0x200; + + /** + * Update flag constant (bit mask value 0x400) indicating that a + * resource should be marked as derived. + * + * @see IFile#create(java.io.InputStream, int, IProgressMonitor) + * @see IFolder#create(int, boolean, IProgressMonitor) + * @see IResource#setDerived(boolean) + * @since 3.2 + */ + public static final int DERIVED = 0x400; + + /** + * Update flag constant (bit mask value 0x800) indicating that a + * resource should be marked as team private. + * + * @see IFile#create(java.io.InputStream, int, IProgressMonitor) + * @see IFolder#create(int, boolean, IProgressMonitor) + * @see IResource#copy(IPath, int, IProgressMonitor) + * @see IResource#setTeamPrivateMember(boolean) + * @since 3.2 + */ + public static final int TEAM_PRIVATE = 0x800; + + /** + * Update flag constant (bit mask value 0x1000) indicating that a + * resource should be marked as a hidden resource. + * + * @since 3.4 + */ + public static final int HIDDEN = 0x1000; + + /** + * Update flag constant (bit mask value 0x2000) indicating that a + * resource should be marked as a virtual resource. + * + * @see IFolder#create(int, boolean, IProgressMonitor) + * @since 3.6 + */ + public static final int VIRTUAL = 0x2000; + + /*==================================================================== + * Other constants: + *====================================================================*/ + + /** + * Modification stamp constant (value -1) indicating no modification stamp is + * available. + * + * @see #getModificationStamp() + */ + public static final int NULL_STAMP = -1; + + /** + * General purpose zero-valued bit mask constant. Useful whenever you need to + * supply a bit mask with no bits set. + *

                      + * Example usage: + * + *

                      +	 *    delete(IResource.NONE, null)
                      +	 * 
                      + * + *

                      + * + * @since 2.0 + */ + public static final int NONE = 0; + + /** + * Accepts the given visitor for an optimized traversal. + * The visitor's visit method is called, and is provided with a + * proxy to this resource. The proxy is a transient object that can be queried + * very quickly for information about the resource. If the actual resource + * handle is needed, it can be obtained from the proxy. Requesting the resource + * handle, or the full path of the resource, will degrade performance of the + * visit. + *

                      + * The entire subtree under the given resource is traversed to infinite depth, + * unless the visitor ignores a subtree by returning false from its + * visit method. + *

                      + *

                      + * This is a convenience method, fully equivalent to + * accept(visitor, IResource.DEPTH_INFINITE, memberFlags). + *

                      + *

                      No guarantees are made about the behavior of this method if resources + * are deleted or added during the traversal of this resource hierarchy. If + * resources are deleted during the traversal, they may still be passed to the + * visitor; if resources are created, they may not be passed to the visitor. If + * resources other than the one being visited are modified during the traversal, + * the resource proxy may contain stale information when that resource is + * visited. + *

                      +

                      + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified in the member + * flags (recommended), only member resources that exist will be visited. + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is specified, the visit will + * also include any phantom member resource that the workspace is keeping track of. + *

                      + *

                      + * If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not specified + * (recommended), team private members will not be visited. If the + * {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the member + * flags, team private member resources are visited as well. + *

                      + *

                      + * If the {@link IContainer#INCLUDE_HIDDEN} flag is not specified (recommended), + * hidden resources will not be visited. If the {@link IContainer#INCLUDE_HIDDEN} flag is specified + * in the member flags, hidden resources are visited as well. + *

                      + *

                      + * If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified (recommended), + * the resource is checked for existence before the visitor's visit + * method is called. If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is specified + * in the member flags, the resource is not checked for existence before the visitor's + * visit method is called. Children of the resource are never checked + * for existence. + *

                      + * + * @param visitor the visitor + * @param memberFlags bit-wise or of member flag constants + * ({@link IContainer#INCLUDE_PHANTOMS}, {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} + * and {@link IContainer#INCLUDE_HIDDEN}) indicating which members are of interest + * and {@link IContainer#DO_NOT_CHECK_EXISTENCE} if the resource on which the method is + * called should not be checked for existence + * @exception CoreException if this request fails. Reasons include: + *
                        + *
                      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource does not exist.
                      • + *
                      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource is a project that is not open.
                      • + *
                      • the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified and + * this resource does not exist.
                      • + *
                      • The visitor failed with this exception.
                      • + *
                      + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IContainer#DO_NOT_CHECK_EXISTENCE + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResourceProxyVisitor#visit(IResourceProxy) + * @since 2.1 + */ + public void accept(IResourceProxyVisitor visitor, int memberFlags) throws CoreException; + + /** + * Accepts the given visitor for an optimized traversal. + * The visitor's visit method is called, and is provided with a + * proxy to this resource. The proxy is a transient object that can be queried + * very quickly for information about the resource. If the actual resource + * handle is needed, it can be obtained from the proxy. Requesting the resource + * handle, or the full path of the resource, will degrade performance of the + * visit. + *

                      + * The entire subtree under the given resource is traversed to the supplied depth, + * unless the visitor ignores a subtree by returning false from its + * visit method. + *

                      + *

                      No guarantees are made about the behavior of this method if resources + * are deleted or added during the traversal of this resource hierarchy. If + * resources are deleted during the traversal, they may still be passed to the + * visitor; if resources are created, they may not be passed to the visitor. If + * resources other than the one being visited are modified during the traversal, + * the resource proxy may contain stale information when that resource is + * visited. + *

                      + *

                      + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified in the member + * flags (recommended), only member resources that exist will be visited. + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is specified, the visit will + * also include any phantom member resource that the workspace is keeping track of. + *

                      + *

                      + * If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not specified + * (recommended), team private members will not be visited. If the + * {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the member + * flags, team private member resources are visited as well. + *

                      + *

                      + * If the {@link IContainer#INCLUDE_HIDDEN} flag is not specified (recommended), + * hidden resources will not be visited. If the {@link IContainer#INCLUDE_HIDDEN} flag is specified + * in the member flags, hidden resources are visited as well. + *

                      + *

                      + * If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified (recommended), + * the resource is checked for existence before the visitor's visit + * method is called. If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is specified + * in the member flags, the resource is not checked for existence before the visitor's + * visit method is called. Children of the resource are never checked + * for existence. + *

                      + * + * @param visitor the visitor + * @param depth the depth to which members of this resource should be + * visited. One of {@link IResource#DEPTH_ZERO}, {@link IResource#DEPTH_ONE}, + * or {@link IResource#DEPTH_INFINITE}. + * @param memberFlags bit-wise or of member flag constants + * ({@link IContainer#INCLUDE_PHANTOMS}, {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} + * and {@link IContainer#INCLUDE_HIDDEN}) indicating which members are of interest + * and {@link IContainer#DO_NOT_CHECK_EXISTENCE} if the resource on which the method is + * called should not be checked for existence + * @exception CoreException if this request fails. Reasons include: + *
                        + *
                      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource does not exist.
                      • + *
                      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource is a project that is not open.
                      • + *
                      • the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified and + * this resource does not exist.
                      • + *
                      • The visitor failed with this exception.
                      • + *
                      + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IContainer#DO_NOT_CHECK_EXISTENCE + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceProxyVisitor#visit(IResourceProxy) + * @since 3.8 + */ + public void accept(IResourceProxyVisitor visitor, int depth, int memberFlags) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource. If the visitor returns true, this method + * visits this resource's members. + *

                      + * This is a convenience method, fully equivalent to + * accept(visitor, IResource.DEPTH_INFINITE, IResource.NONE). + *

                      + * + * @param visitor the visitor + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • The visitor failed with this exception.
                      • + *
                      + * @see IResourceVisitor#visit(IResource) + * @see #accept(IResourceVisitor,int,int) + */ + public void accept(IResourceVisitor visitor) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource. If the visitor returns false, + * this resource's members are not visited. + *

                      + * The subtree under the given resource is traversed to the supplied depth. + *

                      + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   accept(visitor, depth, includePhantoms ? IContainer.INCLUDE_PHANTOMS : IResource.NONE);
                      +	 * 
                      + *

                      + * + * @param visitor the visitor + * @param depth the depth to which members of this resource should be + * visited. One of {@link IResource#DEPTH_ZERO}, {@link IResource#DEPTH_ONE}, + * or {@link IResource#DEPTH_INFINITE}. + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest. + * @exception CoreException if this request fails. Reasons include: + *
                        + *
                      • includePhantoms is false and + * this resource does not exist.
                      • + *
                      • includePhantoms is true and + * this resource does not exist and is not a phantom.
                      • + *
                      • The visitor failed with this exception.
                      • + *
                      + * @see IResource#isPhantom() + * @see IResourceVisitor#visit(IResource) + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResource#accept(IResourceVisitor,int,int) + */ + public void accept(IResourceVisitor visitor, int depth, boolean includePhantoms) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource. If the visitor returns false, + * this resource's members are not visited. + *

                      + * The subtree under the given resource is traversed to the supplied depth. + *

                      + *

                      + * No guarantees are made about the behavior of this method if resources are + * deleted or added during the traversal of this resource hierarchy. If + * resources are deleted during the traversal, they may still be passed to the + * visitor; if resources are created, they may not be passed to the visitor. + *

                      + *

                      + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified in the member + * flags (recommended), only member resources that exists are visited. + * If the {@link IContainer#INCLUDE_PHANTOMS} flag is specified, the visit also + * includes any phantom member resource that the workspace is keeping track of. + *

                      + *

                      + * If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not specified + * (recommended), team private members are not visited. If the + * {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the member + * flags, team private member resources are visited as well. + *

                      + *

                      + * If the {@link IContainer#EXCLUDE_DERIVED} flag is not specified + * (recommended), derived resources are visited. If the + * {@link IContainer#EXCLUDE_DERIVED} flag is specified in the member + * flags, derived resources are not visited. + *

                      + *

                      + * If the {@link IContainer#INCLUDE_HIDDEN} flag is not specified (recommended), + * hidden resources will not be visited. If the {@link IContainer#INCLUDE_HIDDEN} flag is specified + * in the member flags, hidden resources are visited as well. + *

                      + *

                      + * If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified (recommended), + * the resource is checked for existence before the visitor's visit + * method is called. If the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is specified + * in the member flags, the resource is not checked for existence before the visitor's + * visit method is called. Children of the resource are never checked + * for existence. + *

                      + * + * @param visitor the visitor + * @param depth the depth to which members of this resource should be + * visited. One of {@link IResource#DEPTH_ZERO}, {@link IResource#DEPTH_ONE}, + * or {@link IResource#DEPTH_INFINITE}. + * @param memberFlags bit-wise or of member flag constants + * ({@link IContainer#INCLUDE_PHANTOMS}, {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS}, + * {@link IContainer#INCLUDE_HIDDEN} and {@link IContainer#EXCLUDE_DERIVED}) indicating + * which members are of interest and {@link IContainer#DO_NOT_CHECK_EXISTENCE} + * if the resource on which the method is called should not be checked for existence + * @exception CoreException if this request fails. Reasons include: + *
                        + *
                      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource does not exist.
                      • + *
                      • the {@link IContainer#INCLUDE_PHANTOMS} flag is not specified and + * this resource is a project that is not open.
                      • + *
                      • the {@link IContainer#DO_NOT_CHECK_EXISTENCE} flag is not specified and + * this resource does not exist.
                      • + *
                      • The visitor failed with this exception.
                      • + *
                      + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IContainer#EXCLUDE_DERIVED + * @see IContainer#DO_NOT_CHECK_EXISTENCE + * @see IResource#isDerived() + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResource#isHidden() + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceVisitor#visit(IResource) + * @since 2.0 + */ + public void accept(IResourceVisitor visitor, int depth, int memberFlags) throws CoreException; + + /** + * Removes the local history of this resource and its descendents. + *

                      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + */ + public void clearHistory(IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this resource at the given path. + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   copy(destination, (force ? FORCE : IResource.NONE), monitor);
                      +	 * 
                      + *

                      + *

                      + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource copy has been added to its new parent. + *

                      + *

                      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource or one of its descendents is not local.
                      • + *
                      • The source or destination is the workspace root.
                      • + *
                      • The source is a project but the destination is not.
                      • + *
                      • The destination is a project but the source is not.
                      • + *
                      • The resource corresponding to the parent destination path does not exist.
                      • + *
                      • The resource corresponding to the parent destination path is a closed project.
                      • + *
                      • A resource at destination path does exist.
                      • + *
                      • This resource or one of its descendents is out of sync with the local file + * system and force is false.
                      • + *
                      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
                      • + *
                      • The source resource is a file and the destination path specifies a project.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + */ + public void copy(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this resource at the given path. The resource's + * descendents are copied as well. The path of this resource must not be a + * prefix of the destination path. The workspace root may not be the source or + * destination location of a copy operation, and a project can only be copied to + * another project. After successful completion, corresponding new resources + * will exist at the given path; their contents and properties will be copies of + * the originals. The original resources are not affected. + *

                      + * The supplied path may be absolute or relative. Absolute paths fully specify + * the new location for the resource, including its project. Relative paths are + * considered to be relative to the container of the resource being copied. A + * trailing separator is ignored. + *

                      + *

                      + * Calling this method with a one segment absolute destination path is + * equivalent to calling: + *

                      +	 *   copy(workspace.newProjectDescription(folder.getName()),updateFlags,monitor);
                      +	 * 
                      + *

                      + *

                      When a resource is copied, its persistent properties are copied with it. + * Session properties and markers are not copied. + *

                      + *

                      + * The {@link #FORCE} update flag controls how this method deals with cases + * where the workspace is not completely in sync with the local file system. If + * {@link #FORCE} is not specified, the method will only attempt to copy + * resources that are in sync with the corresponding files and directories in + * the local file system; it will fail if it encounters a resource that is out + * of sync with the file system. However, if {@link #FORCE} is specified, + * the method copies all corresponding files and directories from the local file + * system, including ones that have been recently updated or created. Note that + * in both settings of the {@link #FORCE} flag, the operation fails if the + * newly created resources in the workspace would be out of sync with the local + * file system; this ensures files in the file system cannot be accidentally + * overwritten. + *

                      + *

                      + * The {@link #SHALLOW} update flag controls how this method deals with linked + * resources. If {@link #SHALLOW} is not specified, then the underlying + * contents of the linked resource will always be copied in the file system. In + * this case, the destination of the copy will never be a linked resource or + * contain any linked resources. If {@link #SHALLOW} is specified when a + * linked resource is copied into another project, a new linked resource is + * created in the destination project that points to the same file system + * location. When a project containing linked resources is copied, the new + * project will contain the same linked resources pointing to the same file + * system locations. For both of these shallow cases, no files on disk under + * the linked resource are actually copied. With the {@link #SHALLOW} flag, + * copying of linked resources into anything other than a project is not + * permitted. The {@link #SHALLOW} update flag is ignored when copying non- + * linked resources. + *

                      + *

                      + * The {@link #DERIVED} update flag indicates that the new resource + * should immediately be set as a derived resource. Specifying this flag + * is equivalent to atomically calling {@link #setDerived(boolean)} + * with a value of true immediately after creating the resource. + *

                      + *

                      + * The {@link #TEAM_PRIVATE} update flag indicates that the new resource + * should immediately be set as a team private resource. Specifying this flag + * is equivalent to atomically calling {@link #setTeamPrivateMember(boolean)} + * with a value of true immediately after creating the resource. + *

                      + *

                      + * The {@link #HIDDEN} update flag indicates that the new resource + * should immediately be set as a hidden resource. Specifying this flag + * is equivalent to atomically calling {@link #setHidden(boolean)} + * with a value of true immediately after creating the resource. + *

                      + *

                      + * Update flags other than those listed above are ignored. + *

                      + *

                      + * This operation changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resource copy has been added to its new parent. + *

                      + *

                      + * An attempt will be made to copy the local history for this resource and its children, + * to the destination. Since local history existence is a safety-net mechanism, failure + * of this action will not result in automatic failure of the copy operation. + *

                      + *

                      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param destination the destination path + * @param updateFlags bit-wise or of update flag constants + * ({@link #FORCE}, {@link #SHALLOW}, {@link #DERIVED}, {@link #TEAM_PRIVATE}, {@link #HIDDEN}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource or one of its descendents is not local.
                      • + *
                      • The source or destination is the workspace root.
                      • + *
                      • The source is a project but the destination is not.
                      • + *
                      • The destination is a project but the source is not.
                      • + *
                      • The resource corresponding to the parent destination path does not exist.
                      • + *
                      • The resource corresponding to the parent destination path is a closed project.
                      • + *
                      • The source is a linked resource, but the destination is not a project, + * and {@link #SHALLOW} is specified.
                      • + *
                      • A resource at destination path does exist.
                      • + *
                      • This resource or one of its descendants is out of sync with the local file + * system and {@link #FORCE} is not specified.
                      • + *
                      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendants.
                      • + *
                      • The source resource is a file and the destination path specifies a project.
                      • + *
                      • The source is a linked resource, and the destination path does not + * specify a project.
                      • + *
                      • The location of the source resource on disk is the same or a prefix of + * the location of the destination resource on disk.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see #FORCE + * @see #SHALLOW + * @see #DERIVED + * @see #TEAM_PRIVATE + * @see IResourceRuleFactory#copyRule(IResource, IResource) + * @since 2.0 + */ + public void copy(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this project using the given project description. + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   copy(description, (force ? FORCE : IResource.NONE), monitor);
                      +	 * 
                      + *

                      + *

                      + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource copy has been added to its new parent. + *

                      + *

                      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param description the destination project description + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource or one of its descendents is not local.
                      • + *
                      • This resource is not a project.
                      • + *
                      • The project described by the given description already exists.
                      • + *
                      • This resource or one of its descendents is out of sync with the local file + * system and force is false.
                      • + *
                      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + */ + public void copy(IProjectDescription description, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Makes a copy of this project using the given project description. + * The project's descendents are copied as well. The description specifies the + * name, location and attributes of the new project. After successful + * completion, corresponding new resources will exist at the given path; their + * contents and properties will be copies of the originals. The original + * resources are not affected. + *

                      + * When a resource is copied, its persistent properties are copied with it. + * Session properties and markers are not copied. + *

                      + *

                      The {@link #FORCE} update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local file + * system. If {@link #FORCE} is not specified, the method will only attempt + * to copy resources that are in sync with the corresponding files and + * directories in the local file system; it will fail if it encounters a + * resource that is out of sync with the file system. However, if + * {@link #FORCE} is specified, the method copies all corresponding files + * and directories from the local file system, including ones that have been + * recently updated or created. Note that in both settings of the + * {@link #FORCE} flag, the operation fails if the newly created resources + * in the workspace would be out of sync with the local file system; this + * ensures files in the file system cannot be accidentally overwritten. + *

                      + *

                      + * The {@link #SHALLOW} update flag controls how this method deals with + * linked resources. If {@link #SHALLOW} is not specified, then the + * underlying contents of any linked resources in the project will always be + * copied in the file system. In this case, the destination of the copy will + * never contain any linked resources. If {@link #SHALLOW} is specified + * when a project containing linked resources is copied, new linked resources + * are created in the destination project that point to the same file system + * locations. In this case, no files on disk under linked resources are + * actually copied. The {@link #SHALLOW} update flag is ignored when copying + * non- linked resources. + *

                      + *

                      + * Update flags other than {@link #FORCE} or {@link #SHALLOW} are ignored. + *

                      + *

                      + * An attempt will be made to copy the local history for this resource and its children, + * to the destination. Since local history existence is a safety-net mechanism, failure + * of this action will not result in automatic failure of the copy operation. + *

                      + *

                      This operation changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resource copy has been added to its new parent. + *

                      + *

                      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param description the destination project description + * @param updateFlags bit-wise or of update flag constants + * ({@link #FORCE} and {@link #SHALLOW}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be copied. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource or one of its descendents is not local.
                      • + *
                      • This resource is not a project.
                      • + *
                      • The project described by the given description already exists.
                      • + *
                      • This resource or one of its descendents is out of sync with the local file + * system and {@link #FORCE} is not specified.
                      • + *
                      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see #FORCE + * @see #SHALLOW + * @see IResourceRuleFactory#copyRule(IResource, IResource) + * @since 2.0 + */ + public void copy(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Creates and returns the marker with the specified type on this resource. + * Marker type ids should be the id of an extension installed in the + * org.eclipse.core.resources.markers extension + * point. The specified type string must not be null. + * + * @param type the type of the marker to create + * @return the handle of the new marker + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public IMarker createMarker(String type) throws CoreException; + + /** + * Creates a resource proxy representing the current state of this resource. + *

                      + * Note that once a proxy has been created, it does not stay in sync + * with the corresponding resource. Changes to the resource after + * the proxy is created will not be reflected in the state of the proxy. + *

                      + * + * @return A proxy representing this resource + * @since 3.2 + */ + public IResourceProxy createProxy(); + + /** + * Deletes this resource from the workspace. + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   delete(force ? FORCE : IResource.NONE, monitor);
                      +	 * 
                      + *

                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource could not be deleted for some reason.
                      • + *
                      • This resource or one of its descendents is out of sync with the local file system + * and force is false.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * + * @see IResource#delete(int,IProgressMonitor) + */ + public void delete(boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes this resource from the workspace. + * Deletion applies recursively to all members of this resource in a "best- + * effort" fashion. That is, all resources which can be deleted are deleted. + * Resources which could not be deleted are noted in a thrown exception. The + * method does not fail if resources do not exist; it fails only if resources + * could not be deleted. + *

                      + * Deleting a non-linked resource also deletes its contents from the local file + * system. In the case of a file or folder resource, the corresponding file or + * directory in the local file system is deleted. Deleting an open project + * recursively deletes its members; deleting a closed project just gets rid of + * the project itself (closed projects have no members); files in the project's + * local content area are retained; referenced projects are unaffected. + *

                      + *

                      + * Deleting a linked resource does not delete its contents from the file system, + * it just removes that resource and its children from the workspace. Deleting + * children of linked resources does remove the contents from the file system. + *

                      + *

                      + * Deleting a resource also deletes its session and persistent properties and + * markers. + *

                      + *

                      + * Deleting a non-project resource which has sync information converts the + * resource to a phantom and retains the sync information for future use. + *

                      + *

                      + * Deleting the workspace root resource recursively deletes all projects, + * and removes all markers, properties, sync info and other data related to the + * workspace root; the root resource itself is not deleted, however. + *

                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + *

                      + * The {@link #FORCE} update flag controls how this method deals with + * cases where the workspace is not completely in sync with the local + * file system. If {@link #FORCE} is not specified, the method will only + * attempt to delete files and directories in the local file system that + * correspond to, and are in sync with, resources in the workspace; it will fail + * if it encounters a file or directory in the file system that is out of sync + * with the workspace. This option ensures there is no unintended data loss; + * it is the recommended setting. However, if {@link #FORCE} is specified, + * the method will ruthlessly attempt to delete corresponding files and + * directories in the local file system, including ones that have been recently + * updated or created. + *

                      + *

                      + * The {@link #KEEP_HISTORY} update flag controls whether or not files that + * are about to be deleted from the local file system have their current + * contents saved in the workspace's local history. The local history mechanism + * serves as a safety net to help the user recover from mistakes that might + * otherwise result in data loss. Specifying {@link #KEEP_HISTORY} is + * recommended except in circumstances where past states of the files are of no + * conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. Hence {@link #KEEP_HISTORY} is only really applicable + * when deleting files and folders, but not projects. + *

                      + *

                      + * The {@link #ALWAYS_DELETE_PROJECT_CONTENT} update flag controls how + * project deletions are handled. If {@link #ALWAYS_DELETE_PROJECT_CONTENT} + * is specified, then the files and folders in a project's local content area + * are deleted, regardless of whether the project is open or closed; + * {@link #FORCE} is assumed regardless of whether it is specified. If + * {@link #NEVER_DELETE_PROJECT_CONTENT} is specified, then the files and + * folders in a project's local content area are retained, regardless of whether + * the project is open or closed; the {@link #FORCE} flag is ignored. If + * neither of these flags is specified, files and folders in a project's local + * content area from open projects (subject to the {@link #FORCE} flag), but + * never from closed projects. + *

                      + * + * @param updateFlags bit-wise or of update flag constants ( + * {@link #FORCE}, {@link #KEEP_HISTORY}, + * {@link #ALWAYS_DELETE_PROJECT_CONTENT}, + * and {@link #NEVER_DELETE_PROJECT_CONTENT}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource could not be deleted for some reason.
                      • + *
                      • This resource or one of its descendents is out of sync with the local file system + * and {@link #FORCE} is not specified.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see IFile#delete(boolean, boolean, IProgressMonitor) + * @see IFolder#delete(boolean, boolean, IProgressMonitor) + * @see #FORCE + * @see #KEEP_HISTORY + * @see #ALWAYS_DELETE_PROJECT_CONTENT + * @see #NEVER_DELETE_PROJECT_CONTENT + * @see IResourceRuleFactory#deleteRule(IResource) + * @since 2.0 + */ + public void delete(int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes all markers on this resource of the given type, and, + * optionally, deletes such markers from its children. If includeSubtypes + * is false, only markers whose type exactly matches + * the given type are deleted. + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

                      + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param depth how far to recurse (see IResource.DEPTH_* ) + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException; + + /** + * Compares two objects for equality; + * for resources, equality is defined in terms of their handles: + * same resource type, equal full paths, and identical workspaces. + * Resources are not equal to objects other than resources. + * + * @param other the other object + * @return an indication of whether the objects are equals + * @see #getType() + * @see #getFullPath() + * @see #getWorkspace() + */ + @Override + public boolean equals(Object other); + + /** + * Returns whether this resource exists in the workspace. + *

                      + * IResource objects are lightweight handle objects + * used to access resources in the workspace. However, having a + * handle object does not necessarily mean the workspace really + * has such a resource. When the workspace does have a genuine + * resource of a matching type, the resource is said to + * exist, and this method returns true; + * in all other cases, this method returns false. + * In particular, it returns false if the workspace + * has no resource at that path, or if it has a resource at that + * path with a type different from the type of this resource handle. + *

                      + *

                      + * Note that no resources ever exist under a project + * that is closed; opening a project may bring some + * resources into existence. + *

                      + *

                      + * The name and path of a resource handle may be invalid. + * However, validation checks are done automatically as a + * resource is created; this means that any resource that exists + * can be safely assumed to have a valid name and path. + *

                      + * + * @return true if the resource exists, otherwise + * false + */ + public boolean exists(); + + /** + * Returns the marker with the specified id on this resource, + * Returns null if there is no matching marker. + * + * @param id the id of the marker to find + * @return a marker or null + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      + */ + public IMarker findMarker(long id) throws CoreException; + + /** + * Returns all markers of the specified type on this resource, + * and, optionally, on its children. If includeSubtypes + * is false, only markers whose type exactly matches + * the given type are returned. Returns an empty array if there + * are no matching markers. + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param depth how far to recurse (see IResource.DEPTH_* ) + * @return an array of markers + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + */ + public IMarker[] findMarkers(String type, boolean includeSubtypes, int depth) throws CoreException; + + /** + * Returns the maximum value of the {@link IMarker#SEVERITY} attribute across markers + * of the specified type on this resource, and, optionally, on its children. + * If includeSubtypesis false, only markers whose type + * exactly matches the given type are considered. + * Returns -1 if there are no matching markers. + * Returns {@link IMarker#SEVERITY_ERROR} if any of the markers has a severity + * greater than or equal to {@link IMarker#SEVERITY_ERROR}. + * + * @param type the type of marker to consider (normally {@link IMarker#PROBLEM} + * or one of its subtypes), or null to indicate all types + * + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param depth how far to recurse (see IResource.DEPTH_* ) + * @return {@link IMarker#SEVERITY_INFO}, {@link IMarker#SEVERITY_WARNING}, {@link IMarker#SEVERITY_ERROR}, or -1 + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @since 3.3 + */ + public int findMaxProblemSeverity(String type, boolean includeSubtypes, int depth) throws CoreException; + + /** + * Returns the file extension portion of this resource's name, + * or null if it does not have one. + *

                      + * The file extension portion is defined as the string + * following the last period (".") character in the name. + * If there is no period in the name, the path has no + * file extension portion. If the name ends in a period, + * the file extension portion is the empty string. + *

                      + *

                      + * This is a resource handle operation; the resource need + * not exist. + *

                      + * + * @return a string file extension + * @see #getName() + */ + public String getFileExtension(); + + /** + * Returns the full, absolute path of this resource relative to the + * workspace. + *

                      + * This is a resource handle operation; the resource need + * not exist. + * If this resource does exist, its path can be safely assumed to be valid. + *

                      + *

                      + * A resource's full path indicates the route from the root of the workspace + * to the resource. Within a workspace, there is exactly one such path + * for any given resource. The first segment of these paths name a project; + * remaining segments, folders and/or files within that project. + * The returned path never has a trailing separator. The path of the + * workspace root is Path.ROOT. + *

                      + *

                      + * Since absolute paths contain the name of the project, they are + * vulnerable when the project is renamed. For most situations, + * project-relative paths are recommended over absolute paths. + *

                      + * + * @return the absolute path of this resource + * @see #getProjectRelativePath() + * @see Path#ROOT + */ + public IPath getFullPath(); + + /** + * Returns a cached value of the local time stamp on disk for this resource, or + * {@link #NULL_STAMP} if the resource does not exist or is not local or is + * not accessible. The return value is represented as the number of milliseconds + * since the epoch (00:00:00 GMT, January 1, 1970). + * The returned value may not be the same as the actual time stamp + * on disk if the file has been modified externally since the last local refresh. + *

                      + * Note that due to varying file system timing granularities, this value is not guaranteed + * to change every time the file is modified. For a more reliable indication of whether + * the file has changed, use {@link #getModificationStamp}. + * + * @return a local file system time stamp, or {@link #NULL_STAMP}. + * @since 3.0 + */ + public long getLocalTimeStamp(); + + /** + * Returns the absolute path in the local file system to this resource, + * or null if no path can be determined. + *

                      + * If this resource is the workspace root, this method returns + * the absolute local file system path of the platform working area. + *

                      + * If this resource is a project that exists in the workspace, this method + * returns the path to the project's local content area. This is true regardless + * of whether the project is open or closed. This value will be null in the case + * where the location is relative to an undefined workspace path variable. + *

                      + * If this resource is a linked resource under a project that is open, this + * method returns the resolved path to the linked resource's local contents. + * This value will be null in the case where the location is relative to an + * undefined workspace path variable. + *

                      + * If this resource is a file or folder under a project that exists, or a + * linked resource under a closed project, this method returns a (non- + * null) path computed from the location of the project's local + * content area and the project- relative path of the file or folder. This is + * true regardless of whether the file or folders exists, or whether the project + * is open or closed. In the case of linked resources, the location of a linked resource + * within a closed project is too computed from the location of the + * project's local content area and the project-relative path of the resource. If the + * linked resource resides in an open project then its location is computed + * according to the link. + *

                      + * If this resource is a project that does not exist in the workspace, + * or a file or folder below such a project, this method returns + * null. This method also returns null if called + * on a resource that is not stored in the local file system. For such resources + * {@link #getLocationURI()} should be used instead. + *

                      + * + * @return the absolute path of this resource in the local file system, + * or null if no path can be determined + * @see #getRawLocation() + * @see #getLocationURI() + * @see IProjectDescription#setLocation(IPath) + * @see Platform#getLocation() + */ + public IPath getLocation(); + + /** + * Returns the absolute URI of this resource, + * or null if no URI can be determined. + *

                      + * If this resource is the workspace root, this method returns + * the absolute location of the platform working area. + *

                      + * If this resource is a project that exists in the workspace, this method + * returns the URI to the project's local content area. This is true regardless + * of whether the project is open or closed. This value will be null in the case + * where the location is relative to an undefined workspace path variable. + *

                      + * If this resource is a linked resource under a project that is open, this + * method returns the resolved URI to the linked resource's local contents. + * This value will be null in the case where the location is relative to an + * undefined workspace path variable. + *

                      + * If this resource is a file or folder under a project that exists, or a + * linked resource under a closed project, this method returns a (non- + * null) URI computed from the location of the project's local + * content area and the project- relative path of the file or folder. This is + * true regardless of whether the file or folders exists, or whether the project + * is open or closed. In the case of linked resources, the location of a linked resource + * within a closed project is computed from the location of the + * project's local content area and the project-relative path of the resource. If the + * linked resource resides in an open project then its location is computed + * according to the link. + *

                      + * If this resource is a project that does not exist in the workspace, + * or a file or folder below such a project, this method returns + * null. + *

                      + * + * @return the absolute URI of this resource, + * or null if no URI can be determined + * @see #getRawLocation() + * @see IProjectDescription#setLocation(IPath) + * @see Platform#getLocation() + * @see java.net.URI + * @since 3.2 + */ + public URI getLocationURI(); + + /** + * Returns a marker handle with the given id on this resource. + * This resource is not checked to see if it has such a marker. + * The returned marker need not exist. + * This resource need not exist. + * + * @param id the id of the marker + * @return the specified marker handle + * @see IMarker#getId() + */ + public IMarker getMarker(long id); + + /** + * Returns a non-negative modification stamp, or {@link #NULL_STAMP} if + * the resource does not exist or is not local or is not accessible. + *

                      + * A resource's modification stamp gets updated each time a resource is modified. + * If a resource's modification stamp is the same, the resource has not changed. + * Conversely, if a resource's modification stamp is different, some aspect of it + * (other than properties) has been modified at least once (possibly several times). + * Resource modification stamps are preserved across project close/re-open, + * and across workspace shutdown/restart. + * The magnitude or sign of the numerical difference between two modification stamps + * is not significant. + *

                      + *

                      + * The following things affect a resource's modification stamp: + *

                        + *
                      • creating a non-project resource (changes from {@link #NULL_STAMP})
                      • + *
                      • changing the contents of a file
                      • + *
                      • touching a resource
                      • + *
                      • setting the attributes of a project presented in a project description
                      • + *
                      • deleting a resource (changes to {@link #NULL_STAMP})
                      • + *
                      • moving a resource (source changes to {@link #NULL_STAMP}, + destination changes from {@link #NULL_STAMP})
                      • + *
                      • copying a resource (destination changes from {@link #NULL_STAMP})
                      • + *
                      • making a resource local
                      • + *
                      • closing a project (changes to {@link #NULL_STAMP})
                      • + *
                      • opening a project (changes from {@link #NULL_STAMP})
                      • + *
                      • adding or removing a project nature (changes from {@link #NULL_STAMP})
                      • + *
                      + * The following things do not affect a resource's modification stamp: + *
                        + *
                      • "reading" a resource
                      • + *
                      • adding or removing a member of a project or folder
                      • + *
                      • setting a session property
                      • + *
                      • setting a persistent property
                      • + *
                      • saving the workspace
                      • + *
                      • shutting down and re-opening a workspace
                      • + *
                      + *

                      + * + * @return the modification stamp, or {@link #NULL_STAMP} if this resource either does + * not exist or exists as a closed project + * @see IResource#NULL_STAMP + * @see #revertModificationStamp(long) + */ + public long getModificationStamp(); + + /** + * Returns the name of this resource. + * The name of a resource is synonymous with the last segment + * of its full (or project-relative) path for all resources other than the + * workspace root. The workspace root's name is the empty string. + *

                      + * This is a resource handle operation; the resource need + * not exist. + *

                      + *

                      + * If this resource exists, its name can be safely assumed to be valid. + *

                      + * + * @return the name of the resource + * @see #getFullPath() + * @see #getProjectRelativePath() + */ + public String getName(); + + /** + * Returns the path variable manager for this resource. + * + * @return the path variable manager + * @see IPathVariableManager + * @since 3.6 + */ + public IPathVariableManager getPathVariableManager(); + + /** + * Returns the resource which is the parent of this resource, + * or null if it has no parent (that is, this + * resource is the workspace root). + *

                      + * The full path of the parent resource is the same as this + * resource's full path with the last segment removed. + *

                      + *

                      + * This is a resource handle operation; neither the resource + * nor the resulting resource need exist. + *

                      + * + * @return the parent resource of this resource, + * or null if it has no parent + */ + public IContainer getParent(); + + /** + * Returns a copy of the map of this resource's persistent properties. + * Returns an empty map if this resource has no persistent properties. + * + * @return the map containing the persistent properties where the key is + * the {@link QualifiedName} of the property and the value is the {@link String} + * value of the property. + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is not local.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      + * @see #setPersistentProperty(QualifiedName, String) + * @since 3.4 + */ + public Map getPersistentProperties() throws CoreException; + + /** + * Returns the value of the persistent property of this resource identified + * by the given key, or null if this resource has no such property. + * + * @param key the qualified name of the property + * @return the string value of the property, + * or null if this resource has no such property + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is not local.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      + * @see #setPersistentProperty(QualifiedName, String) + */ + public String getPersistentProperty(QualifiedName key) throws CoreException; + + /** + * Returns the project which contains this resource. + * Returns itself for projects and null + * for the workspace root. + *

                      + * A resource's project is the one named by the first segment + * of its full path. + *

                      + *

                      + * This is a resource handle operation; neither the resource + * nor the resulting project need exist. + *

                      + * + * @return the project handle + */ + public IProject getProject(); + + /** + * Returns a relative path of this resource with respect to its project. + * Returns the empty path for projects and the workspace root. + *

                      + * This is a resource handle operation; the resource need not exist. + * If this resource does exist, its path can be safely assumed to be valid. + *

                      + *

                      + * A resource's project-relative path indicates the route from the project + * to the resource. Within a project, there is exactly one such path + * for any given resource. The returned path never has a trailing slash. + *

                      + *

                      + * Project-relative paths are recommended over absolute paths, since + * the former are not affected if the project is renamed. + *

                      + * + * @return the relative path of this resource with respect to its project + * @see #getFullPath() + * @see #getProject() + * @see Path#EMPTY + */ + public IPath getProjectRelativePath(); + + /** + * Returns the file system location of this resource, or null if no + * path can be determined. The returned path will either be an absolute file + * system path, or a relative path whose first segment is the name of a + * workspace path variable. + *

                      + * If this resource is an existing project, the returned path will be equal to + * the location path in the project description. If this resource is a linked + * resource in an open project, the returned path will be equal to the location + * path supplied when the linked resource was created. In all other cases, this + * method returns the same value as {@link #getLocation()}. + *

                      + * + * @return the raw path of this resource in the local file system, or + * null if no path can be determined + * @see #getLocation() + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @see IPathVariableManager + * @see IProjectDescription#getLocation() + * @since 2.1 + */ + public IPath getRawLocation(); + + /** + * Returns the raw location of this resource, or null if no + * path can be determined. The returned path will either be an absolute URI, + * or a relative URI whose first path segment is the name of a workspace path variable. + * Since the returned location may contain unresolved variables, the resulting URI + * is typically only suitable for display. To access or manipulate the actual resource + * backing location, clients should obtain the resolved location using {@link #getLocationURI()}. + *

                      + * If this resource is an existing project, the returned location will be equal to + * the location URI in the project description. If this resource is a linked + * resource in an open project, the returned location will be equal to the location URI + * supplied when the linked resource was created. In all other cases, this + * method returns the same value as {@link #getLocationURI()}. + *

                      + * + * @return the raw location of this resource, or null if no + * location can be determined + * @see #getLocationURI() + * @see IFile#createLink(URI, int, IProgressMonitor) + * @see IFolder#createLink(URI, int, IProgressMonitor) + * @see IPathVariableManager + * @see IProjectDescription#getLocationURI() + * @since 3.2 + */ + public URI getRawLocationURI(); + + /** + * Gets this resource's extended attributes from the file system, + * or null if the attributes could not be obtained. + *

                      + * Reasons for a null return value include: + *

                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is not local.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      + *

                      + * Attributes that are not supported by the underlying file system + * will have a value of false. + *

                      + * Sample usage:
                      + *
                      + * + * IResource resource;
                      + * ...
                      + * ResourceAttributes attributes = resource.getResourceAttributes();
                      + * if (attributes != null) { + * attributes.setExecutable(true);
                      + * resource.setResourceAttributes(attributes);
                      + * } + *
                      + *

                      + * + * @return the extended attributes from the file system, or + * null if they could not be obtained + * @see #setResourceAttributes(ResourceAttributes) + * @see ResourceAttributes + * @since 3.1 + */ + public ResourceAttributes getResourceAttributes(); + + /** + * Returns a copy of the map of this resource's session properties. + * Returns an empty map if this resource has no session properties. + * + * @return the map containing the session properties where the key is + * the {@link QualifiedName} of the property and the value is the property + * value (an {@link Object}. + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is not local.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      + * @see #setSessionProperty(QualifiedName, Object) + * @since 3.4 + */ + public Map getSessionProperties() throws CoreException; + + /** + * Returns the value of the session property of this resource identified + * by the given key, or null if this resource has no such property. + * + * @param key the qualified name of the property + * @return the value of the session property, + * or null if this resource has no such property + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is not local.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      + * @see #setSessionProperty(QualifiedName, Object) + */ + public Object getSessionProperty(QualifiedName key) throws CoreException; + + /** + * Returns the type of this resource. + * The returned value will be one of {@link #FILE}, {@link #FOLDER}, {@link #PROJECT}, {@link #ROOT}. + *

                      + *

                        + *
                      • All resources of type {@link #FILE} implement {@link IFile}.
                      • + *
                      • All resources of type {@link #FOLDER} implement {@link IFolder}.
                      • + *
                      • All resources of type {@link #PROJECT} implement {@link IProject}.
                      • + *
                      • All resources of type {@link #ROOT} implement {@link IWorkspaceRoot}.
                      • + *
                      + *

                      + *

                      + * This is a resource handle operation; the resource need not exist in the workspace. + *

                      + * + * @return the type of this resource + * @see #FILE + * @see #FOLDER + * @see #PROJECT + * @see #ROOT + */ + public int getType(); + + /** + * Returns the workspace which manages this resource. + *

                      + * This is a resource handle operation; the resource need not exist in the workspace. + *

                      + * + * @return the workspace + */ + public IWorkspace getWorkspace(); + + /** + * Returns whether this resource is accessible. For files and folders, + * this is equivalent to existing; for projects, + * this is equivalent to existing and being open. The workspace root + * is always accessible. + * + * @return true if this resource is accessible, and false otherwise + * @see #exists() + * @see IProject#isOpen() + */ + public boolean isAccessible(); + + /** + * Returns whether this resource subtree is marked as derived. Returns + * false if this resource does not exist. + * + *

                      + * This is a convenience method, + * fully equivalent to isDerived(IResource.NONE). + *

                      + * + * @return true if this resource is marked as derived, and + * false otherwise + * @see #setDerived(boolean) + * @since 2.0 + */ + public boolean isDerived(); + + /** + * Returns whether this resource subtree is marked as derived. Returns + * false if this resource does not exist. + * + *

                      + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true, if this resource, or any parent resource, is marked + * as derived. If the {@link #CHECK_ANCESTORS} option flag is not specified, + * this method returns false for children of derived resources. + *

                      + * + * @param options bit-wise or of option flag constants (only {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource subtree is derived, and false otherwise + * @see IResource#setDerived(boolean) + * @since 3.4 + */ + public boolean isDerived(int options); + + /** + * Returns whether this resource is hidden in the resource tree. Returns + * false if this resource does not exist. + *

                      + * This operation is not related to the file system hidden attribute accessible using + * {@link ResourceAttributes#isHidden()}. + *

                      + * + * @return true if this resource is hidden, and false otherwise + * @see #setHidden(boolean) + * @since 3.4 + */ + public boolean isHidden(); + + /** + * Returns whether this resource is hidden in the resource tree. Returns + * false if this resource does not exist. + *

                      + * This operation is not related to the file system hidden attribute + * accessible using {@link ResourceAttributes#isHidden()}. + *

                      + *

                      + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true if this resource, or any parent resource, is a hidden + * resource. If the {@link #CHECK_ANCESTORS} option flag is not specified, + * this method returns false for children of hidden resources. + *

                      + * + * @param options + * bit-wise or of option flag constants (only + * {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource is hidden , and + * false otherwise + * @see #setHidden(boolean) + * @since 3.5 + */ + public boolean isHidden(int options); + + /** + * Returns whether this resource has been linked to + * a location other than the default location calculated by the platform. + *

                      + * This is a convenience method, fully equivalent to + * isLinked(IResource.NONE). + *

                      + * + * @return true if this resource is linked, and + * false otherwise + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @since 2.1 + */ + public boolean isLinked(); + + /** + * Returns whether this resource is a virtual resource. Returns true + * for folders that have been marked virtual using the {@link #VIRTUAL} update + * flag. Returns false in all other cases, including + * the case where this resource does not exist. The workspace root, projects + * and files currently cannot be made virtual. + * + * @return true if this resource is virtual, and + * false otherwise + * @see IFile#create(java.io.InputStream, int, IProgressMonitor) + * @see #VIRTUAL + * @since 3.6 + */ + public boolean isVirtual(); + + /** + * Returns true if this resource has been linked to + * a location other than the default location calculated by the platform. This + * location can be outside the project's content area or another location + * within the project. Returns false in all other cases, including + * the case where this resource does not exist. The workspace root and + * projects are never linked. + *

                      + * This method returns true for a resource that has been linked using + * the createLink method. + *

                      + *

                      + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true if this resource, or any parent resource, is a linked + * resource. If the {@link #CHECK_ANCESTORS} option flag is not specified, + * this method returns false for children of linked resources. + *

                      + * + * @param options bit-wise or of option flag constants + * (only {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource is linked, and + * false otherwise + * @see IFile#createLink(IPath, int, IProgressMonitor) + * @see IFolder#createLink(IPath, int, IProgressMonitor) + * @since 3.2 + */ + public boolean isLinked(int options); + + /** + * Returns whether this resource and its members (to the + * specified depth) are expected to have their contents (and properties) + * available locally. Returns false in all other cases, + * including the case where this resource does not exist. The workspace + * root and projects are always local. + *

                      + * When a resource is not local, its content and properties are + * unavailable for both reading and writing. + *

                      + * + * @param depth valid values are {@link #DEPTH_ZERO}, {@link #DEPTH_ONE}, or {@link #DEPTH_INFINITE} + * @return true if this resource is local, and false otherwise + * + * @see #setLocal(boolean, int, IProgressMonitor) + * @deprecated This API is no longer in use. Note that this API is unrelated + * to whether the resource is in the local file system versus some other file system. + */ + @Deprecated + public boolean isLocal(int depth); + + /** + * Returns whether this resource is a phantom resource. + *

                      + * The workspace uses phantom resources to remember outgoing deletions and + * incoming additions relative to an external synchronization partner. Phantoms + * appear and disappear automatically as a byproduct of synchronization. + * Since the workspace root cannot be synchronized in this way, it is never a phantom. + * Projects are also never phantoms. + *

                      + *

                      + * The key point is that phantom resources do not exist (in the technical + * sense of exists, which returns false + * for phantoms) are therefore invisible except through a handful of + * phantom-enabled API methods (notably IContainer.members(boolean)). + *

                      + * + * @return true if this resource is a phantom resource, and + * false otherwise + * @see #exists() + * @see IContainer#members(boolean) + * @see IContainer#findMember(String, boolean) + * @see IContainer#findMember(IPath, boolean) + * @see ISynchronizer + */ + public boolean isPhantom(); + + /** + * Returns whether this resource is marked as read-only in the file system. + * + * @return true if this resource is read-only, + * false otherwise + * @deprecated use {@link #getResourceAttributes()} + */ + @Deprecated + public boolean isReadOnly(); + + /** + * Returns whether this resource and its descendents to the given depth + * are considered to be in sync with the local file system. + *

                      + * A resource is considered to be in sync if all of the following + * conditions are true: + *

                        + *
                      • The resource exists in both the workspace and the file system.
                      • + *
                      • The timestamp in the file system has not changed since the + * last synchronization.
                      • + *
                      • The resource in the workspace is of the same type as the corresponding + * file in the file system (they are either both files or both folders).
                      • + *
                      + * A resource is also considered to be in sync if it is missing from both + * the workspace and the file system. In all other cases the resource is + * considered to be out of sync. + *

                      + *

                      + * This operation interrogates files and folders in the local file system; + * depending on the speed of the local file system and the requested depth, + * this operation may be time-consuming. + *

                      + * + * @param depth the depth (one of {@link #DEPTH_ZERO}, {@link #DEPTH_ONE}, or {@link #DEPTH_INFINITE}) + * @return true if this resource and its descendents to the + * specified depth are synchronized, and false in all other + * cases + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see #refreshLocal(int, IProgressMonitor) + * @since 2.0 + */ + public boolean isSynchronized(int depth); + + /** + * Returns whether this resource is a team private member of its parent container. + * Returns false if this resource does not exist. + * + * @return true if this resource is a team private member, and + * false otherwise + * @see #setTeamPrivateMember(boolean) + * @since 2.0 + */ + public boolean isTeamPrivateMember(); + + /** + * Returns whether this resource is a team private member of its parent + * container. Returns false if this resource does not exist. + *

                      + * The {@link #CHECK_ANCESTORS} option flag indicates whether this method + * should consider ancestor resources in its calculation. If the + * {@link #CHECK_ANCESTORS} flag is present, this method will return + * true if this resource, or any parent resource, is a team + * private member. If the {@link #CHECK_ANCESTORS} option flag is not + * specified, this method returns false for children of team private + * members. + *

                      + * + * @param options + * bit-wise or of option flag constants (only + * {@link #CHECK_ANCESTORS} is applicable) + * @return true if this resource is a team private member, and + * false otherwise + * @see #setTeamPrivateMember(boolean) + * @since 3.5 + */ + public boolean isTeamPrivateMember(int options); + + /** + * Moves this resource so that it is located at the given path. + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   move(destination, force ? FORCE : IResource.NONE, monitor);
                      +	 * 
                      + *

                      + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource has been removed from its parent + * and that a corresponding resource has been added to its new parent. + * Additional information provided with resource delta shows that these + * additions and removals are related. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param destination the destination path + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource or one of its descendents is not local.
                      • + *
                      • The source or destination is the workspace root.
                      • + *
                      • The source is a project but the destination is not.
                      • + *
                      • The destination is a project but the source is not.
                      • + *
                      • The resource corresponding to the parent destination path does not exist.
                      • + *
                      • The resource corresponding to the parent destination path is a closed + * project.
                      • + *
                      • A resource at destination path does exist.
                      • + *
                      • A resource of a different type exists at the destination path.
                      • + *
                      • This resource or one of its descendents is out of sync with the local file + * system and force is false.
                      • + *
                      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      • The source resource is a file and the destination path specifies a project.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + */ + public void move(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Moves this resource so that it is located at the given path. + * The path of the resource must not be a prefix of the destination path. The + * workspace root may not be the source or destination location of a move + * operation, and a project can only be moved to another project. After + * successful completion, the resource and any direct or indirect members will + * no longer exist; but corresponding new resources will now exist at the given + * path. + *

                      + * The supplied path may be absolute or relative. Absolute paths fully specify + * the new location for the resource, including its project. Relative paths are + * considered to be relative to the container of the resource being moved. A + * trailing slash is ignored. + *

                      + *

                      + * Calling this method with a one segment absolute destination path is + * equivalent to calling: + *

                      +	 IProjectDescription description = getDescription();
                      +	 description.setName(path.lastSegment());
                      +	 move(description, updateFlags, monitor);
                      +	 * 
                      + *

                      + *

                      When a resource moves, its session and persistent properties move with + * it. Likewise for all other attributes of the resource including markers. + *

                      + *

                      + * The {@link #FORCE} update flag controls how this method deals with cases + * where the workspace is not completely in sync with the local file system. If + * {@link #FORCE} is not specified, the method will only attempt to move + * resources that are in sync with the corresponding files and directories in + * the local file system; it will fail if it encounters a resource that is out + * of sync with the file system. However, if {@link #FORCE} is specified, + * the method moves all corresponding files and directories from the local file + * system, including ones that have been recently updated or created. Note that + * in both settings of the {@link #FORCE} flag, the operation fails if the + * newly created resources in the workspace would be out of sync with the local + * file system; this ensures files in the file system cannot be accidentally + * overwritten. + *

                      + *

                      + * The {@link #KEEP_HISTORY} update flag controls whether or not + * file that are about to be deleted from the local file system have their + * current contents saved in the workspace's local history. The local history + * mechanism serves as a safety net to help the user recover from mistakes that + * might otherwise result in data loss. Specifying {@link #KEEP_HISTORY} + * is recommended except in circumstances where past states of the files are of + * no conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. Hence {@link #KEEP_HISTORY} is only really applicable + * when moving files and folders, but not whole projects. + *

                      + *

                      + * If this resource is not a project, an attempt will be made to copy the local history + * for this resource and its children, to the destination. Since local history existence + * is a safety-net mechanism, failure of this action will not result in automatic failure + * of the move operation. + *

                      + *

                      + * The {@link #SHALLOW} update flag controls how this method deals with linked + * resources. If {@link #SHALLOW} is not specified, then the underlying + * contents of the linked resource will always be moved in the file system. In + * this case, the destination of the move will never be a linked resource or + * contain any linked resources. If {@link #SHALLOW} is specified when a + * linked resource is moved into another project, a new linked resource is + * created in the destination project that points to the same file system + * location. When a project containing linked resources is moved, the new + * project will contain the same linked resources pointing to the same file + * system locations. For either of these cases, no files on disk under the + * linked resource are actually moved. With the {@link #SHALLOW} flag, + * moving of linked resources into anything other than a project is not + * permitted. The {@link #SHALLOW} update flag is ignored when moving non- + * linked resources. + *

                      + *

                      + * Update flags other than {@link #FORCE}, {@link #KEEP_HISTORY}and + * {@link #SHALLOW} are ignored. + *

                      + *

                      + * This method changes resources; these changes will be reported in a subsequent + * resource change event that will include an indication that the resource has + * been removed from its parent and that a corresponding resource has been added + * to its new parent. Additional information provided with resource delta shows + * that these additions and removals are related. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param destination the destination path + * @param updateFlags bit-wise or of update flag constants + * ({@link #FORCE}, {@link #KEEP_HISTORY} and {@link #SHALLOW}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource or one of its descendents is not local.
                      • + *
                      • The source or destination is the workspace root.
                      • + *
                      • The source is a project but the destination is not.
                      • + *
                      • The destination is a project but the source is not.
                      • + *
                      • The resource corresponding to the parent destination path does not exist.
                      • + *
                      • The resource corresponding to the parent destination path is a closed + * project.
                      • + *
                      • The source is a linked resource, but the destination is not a project + * and {@link #SHALLOW} is specified.
                      • + *
                      • A resource at destination path does exist.
                      • + *
                      • A resource of a different type exists at the destination path.
                      • + *
                      • This resource or one of its descendents is out of sync with the local file system + * and force is false.
                      • + *
                      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
                      • + *
                      • The source resource is a file and the destination path specifies a project.
                      • + *
                      • The location of the source resource on disk is the same or a prefix of + * the location of the destination resource on disk.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + * @see #FORCE + * @see #KEEP_HISTORY + * @see #SHALLOW + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @since 2.0 + */ + public void move(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Renames or relocates this project so that it is the project specified by the given project + * description. + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   move(description, (keepHistory ? KEEP_HISTORY : IResource.NONE) | (force ? FORCE : IResource.NONE), monitor);
                      +	 * 
                      + *

                      + *

                      + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include + * an indication that the resource has been removed from its parent + * and that a corresponding resource has been added to its new parent. + * Additional information provided with resource delta shows that these + * additions and removals are related. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param description the destination project description + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param keepHistory a flag indicating whether or not to keep + * local history for files + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource or one of its descendents is not local.
                      • + *
                      • This resource is not a project.
                      • + *
                      • The project at the destination already exists.
                      • + *
                      • This resource or one of its descendents is out of sync with the local file + * system and force is false.
                      • + *
                      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + */ + public void move(IProjectDescription description, boolean force, boolean keepHistory, IProgressMonitor monitor) throws CoreException; + + /** + * Renames or relocates this project so that it is the project specified by the + * given project description. The description specifies the name and location + * of the new project. After successful completion, the old project + * and any direct or indirect members will no longer exist; but corresponding + * new resources will now exist in the new project. + *

                      + * When a resource moves, its session and persistent properties move with it. + * Likewise for all the other attributes of the resource including markers. + *

                      + *

                      + * When this project's location is the default location, then the directories + * and files on disk are moved to be in the location specified by the given + * description. If the given description specifies the default location for the + * project, the directories and files are moved to the default location. If the name + * in the given description is the same as this project's name and the location + * is different, then the project contents will be moved to the new location. + * In all other cases the directories and files on disk are left untouched. + * Parts of the supplied description other than the name and location are ignored. + *

                      + *

                      + * The {@link #FORCE} update flag controls how this method deals with cases + * where the workspace is not completely in sync with the local file system. If + * {@link #FORCE} is not specified, the method will only attempt to move + * resources that are in sync with the corresponding files and directories in + * the local file system; it will fail if it encounters a resource that is out + * of sync with the file system. However, if {@link #FORCE} is specified, + * the method moves all corresponding files and directories from the local file + * system, including ones that have been recently updated or created. Note that + * in both settings of the {@link #FORCE} flag, the operation fails if the + * newly created resources in the workspace would be out of sync with the local + * file system; this ensures files in the file system cannot be accidentally + * overwritten. + *

                      + *

                      + * The {@link #KEEP_HISTORY} update flag controls whether or not file that + * are about to be deleted from the local file system have their current + * contents saved in the workspace's local history. The local history mechanism + * serves as a safety net to help the user recover from mistakes that might + * otherwise result in data loss. Specifying {@link #KEEP_HISTORY} is + * recommended except in circumstances where past states of the files are of no + * conceivable interest to the user. Note that local history is maintained + * with each individual project, and gets discarded when a project is deleted + * from the workspace. Hence {@link #KEEP_HISTORY} is only really applicable + * when moving files and folders, but not whole projects. + *

                      + *

                      + * Local history information for this project and its children will not be moved to the + * destination. + *

                      + *

                      + * The {@link #SHALLOW} update flag controls how this method deals with linked + * resources. If {@link #SHALLOW} is not specified, then the underlying + * contents of any linked resource will always be moved in the file system. In + * this case, the destination of the move will not contain any linked resources. + * If {@link #SHALLOW} is specified when a project containing linked + * resources is moved, new linked resources are created in the destination + * project pointing to the same file system locations. In this case, no files + * on disk under any linked resource are actually moved. The + * {@link #SHALLOW} update flag is ignored when moving non- linked + * resources. + *

                      + *

                      + * The {@link #REPLACE} update flag controls how this method deals + * with a change of location. If the location changes and the {@link #REPLACE} + * flag is not specified, then the projects contents on disk are moved to the new + * location. If the location changes and the {@link #REPLACE} + * flag is specified, then the project is reoriented to correspond to the new + * location, but no contents are moved on disk. The contents already on + * disk at the new location become the project contents. If the new project + * location does not exist, it will be created. + *

                      + *

                      + * Update flags other than those listed above are ignored. + *

                      + *

                      + * This method changes resources; these changes will be reported in a subsequent + * resource change event that will include an indication that the resource has + * been removed from its parent and that a corresponding resource has been added + * to its new parent. Additional information provided with resource delta shows + * that these additions and removals are related. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param description the destination project description + * @param updateFlags bit-wise or of update flag constants + * ({@link #FORCE}, {@link #KEEP_HISTORY}, {@link #SHALLOW} and {@link #REPLACE}). + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this resource could not be moved. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource or one of its descendents is not local.
                      • + *
                      • This resource is not a project.
                      • + *
                      • The project at the destination already exists.
                      • + *
                      • This resource or one of its descendents is out of sync with the + * local file system and {@link #FORCE} is not specified.
                      • + *
                      • The workspace and the local file system are out of sync + * at the destination resource or one of its descendents.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      • The destination file system location is occupied. When moving a project + * in the file system, the destination directory must either not exist or be empty.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see IResourceDelta#getFlags() + * @see #FORCE + * @see #KEEP_HISTORY + * @see #SHALLOW + * @see #REPLACE + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @since 2.0 + */ + public void move(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Refreshes the resource hierarchy from this resource and its + * children (to the specified depth) relative to the local file system. + * Creations, deletions, and changes detected in the local file system + * will be reflected in the workspace's resource tree. + * This resource need not exist or be local. + *

                      + * This method may discover changes to resources; any such + * changes will be reported in a subsequent resource change event. + *

                      + *

                      + * If a new file or directory is discovered in the local file + * system at or below the location of this resource, + * any parent folders required to contain the new + * resource in the workspace will also be created automatically as required. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param depth valid values are {@link #DEPTH_ZERO}, {@link #DEPTH_ONE}, or {@link #DEPTH_INFINITE} + * @param monitor a progress monitor, or null if progress reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see IResource#DEPTH_ZERO + * @see IResource#DEPTH_ONE + * @see IResource#DEPTH_INFINITE + * @see IResourceRuleFactory#refreshRule(IResource) + */ + public void refreshLocal(int depth, IProgressMonitor monitor) throws CoreException; + + /** + * Reverts this resource's modification stamp. This is intended to be used by + * a client that is rolling back or undoing a previous change to this resource. + *

                      + * It is the caller's responsibility to ensure that the value of the reverted + * modification stamp matches this resource's modification stamp prior to the + * change that has been rolled back. More generally, the caller must ensure + * that the specification of modification stamps outlined in + * getModificationStamp is honored; the modification stamp + * of two distinct resource states should be different if and only if one or more + * of the attributes listed in the specification as affecting the modification + * stamp have changed. + *

                      + * Reverting the modification stamp will not be reported in a + * subsequent resource change event. + *

                      + * Note that a resource's modification stamp is unrelated to the local + * time stamp for this resource on disk, if any. A resource's local time + * stamp is modified using the setLocalTimeStamp method. + * + * @param value A non-negative modification stamp value + * @exception CoreException if this method fails. Reasons include: + *

                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is not local.
                      • + *
                      • This resource is not accessible.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see #getModificationStamp() + * @since 3.1 + */ + public void revertModificationStamp(long value) throws CoreException; + + /** + * Sets whether this resource subtree is marked as derived. + *

                      + * A derived resource is a regular file or folder that is + * created in the course of translating, compiling, copying, or otherwise + * processing other files. Derived resources are not original data, and can be + * recreated from other resources. It is commonplace to exclude derived + * resources from version and configuration management because they would + * otherwise clutter the team repository with version of these ever-changing + * files as each user regenerates them. + *

                      + *

                      + * If a resource or any of its ancestors is marked as derived, a team + * provider should assume that the resource is not under version and + * configuration management by default. That is, the resource + * should only be stored in a team repository if the user explicitly indicates + * that this resource is worth saving. + *

                      + *

                      + * Newly-created resources are not marked as derived; rather, the mark must be + * set explicitly using setDerived(true). Derived marks are maintained + * in the in-memory resource tree, and are discarded when the resources are deleted. + * Derived marks are saved to disk when a project is closed, or when the workspace + * is saved. + *

                      + *

                      + * Projects and the workspace root are never considered derived; attempts to + * mark them as derived are ignored. + *

                      + *

                      + * This operation does not result in a resource change event, and does not + * trigger autobuilds. + *

                      + * + * @param isDerived true if this resource is to be marked + * as derived, and false otherwise + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see #isDerived() + * @since 2.0 + * @deprecated Replaced by {@link #setDerived(boolean, IProgressMonitor)} which + * is a workspace operation and reports changes in resource deltas. + */ + @Deprecated + public void setDerived(boolean isDerived) throws CoreException; + + /** + * Sets whether this resource subtree is marked as derived. + *

                      + * A derived resource is a regular file or folder that is + * created in the course of translating, compiling, copying, or otherwise + * processing other files. Derived resources are not original data, and can be + * recreated from other resources. It is commonplace to exclude derived + * resources from version and configuration management because they would + * otherwise clutter the team repository with version of these ever-changing + * files as each user regenerates them. + *

                      + *

                      + * If a resource or any of its ancestors is marked as derived, a team + * provider should assume that the resource is not under version and + * configuration management by default. That is, the resource + * should only be stored in a team repository if the user explicitly indicates + * that this resource is worth saving. + *

                      + *

                      + * Newly-created resources are not marked as derived; rather, the mark must be + * set explicitly using setDerived(true, IProgressMonitor). Derived marks are maintained + * in the in-memory resource tree, and are discarded when the resources are deleted. + * Derived marks are saved to disk when a project is closed, or when the workspace + * is saved. + *

                      + *

                      + * Projects and the workspace root are never considered derived; attempts to + * mark them as derived are ignored. + *

                      + *

                      + * These changes will be reported in a subsequent resource change event, + * including an indication that this file's derived flag has changed. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param isDerived true if this resource is to be marked + * as derived, and false otherwise + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see #isDerived() + * @see IResourceRuleFactory#derivedRule(IResource) + * @since 3.6 + */ + public void setDerived(boolean isDerived, IProgressMonitor monitor) throws CoreException; + + /** + * Sets whether this resource and its members are hidden in the resource tree. + *

                      + * Hidden resources are invisible to most clients. Newly-created resources + * are not hidden resources by default. + *

                      + *

                      + * The workspace root is never considered hidden resource; + * attempts to mark it as hidden are ignored. + *

                      + *

                      + * This operation does not result in a resource change event, and does not + * trigger autobuilds. + *

                      + *

                      + * This operation is not related to {@link ResourceAttributes#setHidden(boolean)}. + * Whether a resource is hidden in the resource tree is unrelated to whether the + * underlying file is hidden in the file system. + *

                      + * + * @param isHidden true if this resource is to be marked + * as hidden, and false otherwise + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see #isHidden() + * @since 3.4 + */ + public void setHidden(boolean isHidden) throws CoreException; + + /** + * Set whether or not this resource and its members (to the + * specified depth) are expected to have their contents (and properties) + * available locally. The workspace root and projects are always local and + * attempting to set either to non-local (i.e., passing false) + * has no effect on the resource. + *

                      + * When a resource is not local, its content and properties are + * unavailable for both reading and writing. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param flag whether this resource should be considered local + * @param depth valid values are {@link #DEPTH_ZERO}, {@link #DEPTH_ONE}, or {@link #DEPTH_INFINITE} + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See {@link IResourceChangeEvent} for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see #isLocal(int) + * @deprecated This API is no longer in use. Note that this API is unrelated + * to whether the resource is in the local file system versus some other file system. + */ + @Deprecated + public void setLocal(boolean flag, int depth, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the local time stamp on disk for this resource. The time must be represented + * as the number of milliseconds since the epoch (00:00:00 GMT, January 1, 1970). + * Returns the actual time stamp that was recorded. + * Due to varying file system timing granularities, the provided value may be rounded + * or otherwise truncated, so the actual recorded time stamp that is returned may + * not be the same as the supplied value. + * + * @param value a time stamp in milliseconds. + * @return a local file system time stamp. + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is not local.
                      • + *
                      • This resource is not accessible.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @since 3.0 + */ + public long setLocalTimeStamp(long value) throws CoreException; + + /** + * Sets the value of the persistent property of this resource identified + * by the given key. If the supplied value is null, + * the persistent property is removed from this resource. The change + * is made immediately on disk. + *

                      + * Persistent properties are intended to be used by plug-ins to store + * resource-specific information that should be persisted across platform sessions. + * The value of a persistent property is a string that must be short - + * 2KB or less in length. Unlike session properties, persistent properties are + * stored on disk and maintained across workspace shutdown and restart. + *

                      + *

                      + * 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 string value of the property, + * or null if the property is to be removed + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is not local.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see #getPersistentProperty(QualifiedName) + * @see #isLocal(int) + */ + public void setPersistentProperty(QualifiedName key, String value) throws CoreException; + + /** + * Sets or unsets this resource as read-only in the file system. + * + * @param readOnly true to set it to read-only, + * false to unset + * @deprecated use IResource#setResourceAttributes(ResourceAttributes) + */ + @Deprecated + public void setReadOnly(boolean readOnly); + + /** + * Sets this resource with the given extended attributes. This sets the + * attributes in the file system. Only attributes that are supported by + * the underlying file system will be set. + *

                      + * Sample usage:
                      + *
                      + * + * IResource resource;
                      + * ...
                      + * if (attributes != null) { + * attributes.setExecutable(true);
                      + * resource.setResourceAttributes(attributes);
                      + * } + *
                      + *

                      + *

                      + * Note that a resource cannot be converted into a symbolic link by + * setting resource attributes with {@link ResourceAttributes#isSymbolicLink()} + * set to true. + *

                      + * + * @param attributes the attributes to set + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is not local.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      + * @see #getResourceAttributes() + * @since 3.1 + */ + void setResourceAttributes(ResourceAttributes attributes) throws CoreException; + + /** + * Sets the value of the session property of this resource identified + * by the given key. If the supplied value is null, + * the session property is removed from this resource. + *

                      + * Sessions properties are intended to be used as a caching mechanism + * by ISV plug-ins. They allow key-object associations to be stored with + * existing resources in the workspace. These key-value associations are + * maintained in memory (at all times), and the information is lost when a + * resource is deleted from the workspace, when the parent project + * is closed, or when the workspace is closed. + *

                      + *

                      + * 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 session property, + * or null if the property is to be removed + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is not local.
                      • + *
                      • This resource is a project that is not open.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see #getSessionProperty(QualifiedName) + */ + public void setSessionProperty(QualifiedName key, Object value) throws CoreException; + + /** + * Sets whether this resource subtree is a team private member of its parent container. + *

                      + * A team private member resource is a special file or folder created by a team + * provider to hold team-provider-specific information. Resources marked as team private + * members are invisible to most clients. + *

                      + *

                      + * Newly-created resources are not team private members by default; rather, the + * team provider must mark a resource explicitly using + * setTeamPrivateMember(true). Team private member marks are + * maintained in the in-memory resource tree, and are discarded when the + * resources are deleted. Team private member marks are saved to disk when a + * project is closed, or when the workspace is saved. + *

                      + *

                      + * Projects and the workspace root are never considered team private members; + * attempts to mark them as team private are ignored. + *

                      + *

                      + * This operation does not result in a resource change event, and does not + * trigger autobuilds. + *

                      + * + * @param isTeamPrivate true if this resource is to be marked + * as team private, and false otherwise + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @see #isTeamPrivateMember() + * @since 2.0 + */ + public void setTeamPrivateMember(boolean isTeamPrivate) throws CoreException; + + /** + * Marks this resource as having changed even though its content + * may not have changed. This method can be used to trigger + * the rebuilding of resources/structures derived from this resource. + * Touching the workspace root has no effect. + *

                      + * This method changes resources; these changes will be reported + * in a subsequent resource change event. If the resource is a project, + * the change event will indicate a description change. + *

                      + *

                      + * 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 + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • This resource does not exist.
                      • + *
                      • This resource is not local.
                      • + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * @see IResourceRuleFactory#modifyRule(IResource) + * @see IResourceDelta#CONTENT + * @see IResourceDelta#DESCRIPTION + */ + public void touch(IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java new file mode 100644 index 0000000000..d3bdaa1e1a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeEvent.java @@ -0,0 +1,266 @@ +/******************************************************************************* + * 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; + +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * Resource change events describe changes to resources. + *

                      + * There are currently five different types of resource change events: + *

                        + *
                      • + * Before-the-fact batch reports of arbitrary creations, + * deletions and modifications to one or more resources expressed + * as a hierarchical resource delta. Event type is + * PRE_BUILD, and getDelta returns + * the hierarchical delta rooted at the workspace root. + * The getBuildKind method returns + * the kind of build that is about to occur, and the getSource + * method returns the scope of the build (either the workspace or a single project). + * These events are broadcast to interested parties immediately + * before each build operation. If autobuilding is not enabled, these events still + * occur at times when autobuild would have occurred. The workspace is open + * for change during notification of these events. The delta reported in this event + * cycle is identical across all listeners registered for this type of event. + * Resource changes attempted during a PRE_BUILD callback + * must be done in the thread doing the notification. + *
                      • + *
                      • + * After-the-fact batch reports of arbitrary creations, + * deletions and modifications to one or more resources expressed + * as a hierarchical resource delta. Event type is + * POST_BUILD, and getDelta returns + * the hierarchical delta rooted at the workspace root. + * The getBuildKind method returns + * the kind of build that occurred, and the getSource + * method returns the scope of the build (either the workspace or a single project). + * These events are broadcast to interested parties at the end of every build operation. + * If autobuilding is not enabled, these events still occur at times when autobuild + * would have occurred. The workspace is open for change during notification of + * these events. The delta reported in this event cycle is identical across + * all listeners registered for this type of event. + * Resource changes attempted during a POST_BUILD callback + * must be done in the thread doing the notification. + *
                      • + *
                      • + * After-the-fact batch reports of arbitrary creations, + * deletions and modifications to one or more resources expressed + * as a hierarchical resource delta. Event type is + * POST_CHANGE, and getDelta returns + * the hierarchical delta. The resource delta is rooted at the + * workspace root. These events are broadcast to interested parties after + * a set of resource changes and happen whether or not autobuilding is enabled. + * The workspace is closed for change during notification of these events. + * The delta reported in this event cycle is identical across all listeners registered for + * this type of event. + *
                      • + *
                      • + * Before-the-fact reports of the impending closure of a single + * project. Event type is PRE_CLOSE, + * and getResource returns the project being closed. + * The workspace is closed for change during notification of these events. + *
                      • + *
                      • + * Before-the-fact reports of the impending deletion of a single + * project. Event type is PRE_DELETE, + * and getResource returns the project being deleted. + * The workspace is closed for change during notification of these events. + *
                      • + *
                      • + * Before-the-fact reports of the impending refresh of a single project or the workspace. + * Event type is PRE_REFRESH and the getSource + * method returns the scope of the refresh (either the workspace or a single project). + * If the event is fired by a project refresh the getResource + * method returns the project being refreshed. + * The workspace is closed for changes during notification of these events. + *
                      • + *
                      + *

                      + * In order to handle additional event types that may be introduced + * in future releases of the platform, clients should do not write code + * that presumes the set of event types is closed. + *

                      + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceChangeEvent { + /** + * Event type constant (bit mask) indicating an after-the-fact + * report of creations, deletions, and modifications + * to one or more resources expressed as a hierarchical + * resource delta as returned by getDelta. + * See class comments for further details. + * + * @see #getType() + * @see #getDelta() + */ + public static final int POST_CHANGE = 1; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of the impending closure of a single + * project as returned by getResource. + * See class comments for further details. + * + * @see #getType() + * @see #getResource() + */ + public static final int PRE_CLOSE = 2; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of the impending deletion of a single + * project as returned by getResource. + * See class comments for further details. + * + * @see #getType() + * @see #getResource() + */ + public static final int PRE_DELETE = 4; + + /** + * @deprecated This event type has been renamed to + * PRE_BUILD + */ + @Deprecated + public static final int PRE_AUTO_BUILD = 8; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of a build. The event contains a hierarchical resource delta + * as returned by getDelta. + * See class comments for further details. + * + * @see #getBuildKind() + * @see #getSource() + * @since 3.0 + */ + public static final int PRE_BUILD = 8; + + /** + * @deprecated This event type has been renamed to + * POST_BUILD + */ + @Deprecated + public static final int POST_AUTO_BUILD = 16; + + /** + * Event type constant (bit mask) indicating an after-the-fact + * report of a build. The event contains a hierarchical resource delta + * as returned by getDelta. + * See class comments for further details. + * + * @see #getBuildKind() + * @see #getSource() + * @since 3.0 + */ + public static final int POST_BUILD = 16; + + /** + * Event type constant (bit mask) indicating a before-the-fact + * report of refreshing the workspace or a project. + * See class comments for further details. + * + * @see #getType() + * @see #getSource() + * @see #getResource() + * @since 3.4 + */ + public static final int PRE_REFRESH = 32; + + /** + * Returns all marker deltas of the specified type that are associated + * with resource deltas for this event. If includeSubtypes + * is false, only marker deltas whose type exactly matches + * the given type are returned. Returns an empty array if there + * are no matching marker deltas. + *

                      + * Calling this method is equivalent to walking the entire resource + * delta for this event, and collecting all marker deltas of a given type. + * The speed of this method will be proportional to the number of changed + * markers, regardless of the size of the resource delta tree. + *

                      + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @return an array of marker deltas + * @since 2.0 + */ + public IMarkerDelta[] findMarkerDeltas(String type, boolean includeSubtypes); + + /** + * Returns the kind of build that caused this event, + * or 0 if not applicable to this type of event. + *

                      + * If the event is a PRE_BUILD or POST_BUILD + * then this will be the kind of build that occurred to cause the event. + *

                      + * + * @see IProject#build(int, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + * @see IncrementalProjectBuilder#AUTO_BUILD + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @return the kind of build, or 0 if not applicable + * @since 3.1 + */ + public int getBuildKind(); + + /** + * Returns a resource delta, rooted at the workspace, describing the set + * of changes that happened to resources in the workspace. + * Returns null if not applicable to this type of event. + * + * @return the resource delta, or null if not + * applicable + */ + public IResourceDelta getDelta(); + + /** + * Returns the resource in question or null + * if not applicable to this type of event. + *

                      + * If the event is of type PRE_CLOSE, + * PRE_DELETE, or PRE_REFRESH, then the resource + * will be the affected project. Otherwise the resource will be null. + *

                      + * @return the resource, or null if not applicable + */ + public IResource getResource(); + + /** + * Returns an object identifying the source of this event. + *

                      + * If the event is a PRE_BUILD, POST_BUILD, + * or PRE_REFRESH then this will be the scope of the build + * (either the {@link IWorkspace} or a single {@link IProject}). + *

                      + * + * @return an object identifying the source of this event + * @see java.util.EventObject + */ + public Object getSource(); + + /** + * Returns the type of event being reported. + * + * @return one of the event type constants + * @see #POST_CHANGE + * @see #POST_BUILD + * @see #PRE_BUILD + * @see #PRE_CLOSE + * @see #PRE_DELETE + * @see #PRE_REFRESH + */ + public int getType(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java new file mode 100644 index 0000000000..f441aa58d5 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceChangeListener.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.util.EventListener; + +/** + * A resource change listener is notified of changes to resources + * in the workspace. + * These changes arise from direct manipulation of resources, or + * indirectly through re-synchronization with the local file system. + *

                      + * Clients may implement this interface. + *

                      + * @see IResourceDelta + * @see IWorkspace#addResourceChangeListener(IResourceChangeListener, int) + */ +public interface IResourceChangeListener extends EventListener { + /** + * Notifies this listener that some resource changes + * are happening, or have already happened. + *

                      + * The supplied event gives details. This event object (and the + * resource delta within it) is valid only for the duration of + * the invocation of this method. + *

                      + *

                      + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

                      + * Note that during resource change event notification, further changes + * to resources may be disallowed. + *

                      + * + * @param event the resource change event + * @see IResourceDelta + */ + public void resourceChanged(IResourceChangeEvent event); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java new file mode 100644 index 0000000000..a12076fb74 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java @@ -0,0 +1,591 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.internal.watson.IElementComparator; +import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory; +import org.eclipse.core.runtime.*; + +/** + * A resource delta represents changes in the state of a resource tree + * between two discrete points in time. + *

                      + * Resource deltas implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

                      + * + * @see IResource + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceDelta extends IAdaptable { + + /*==================================================================== + * Constants defining resource delta kinds: + *====================================================================*/ + + /** + * Delta kind constant indicating that the resource has not been changed in any way. + * + * @see IResourceDelta#getKind() + */ + public static final int NO_CHANGE = IElementComparator.K_NO_CHANGE; + + /** + * Delta kind constant (bit mask) indicating that the resource has been added + * to its parent. That is, one that appears in the "after" state, + * not in the "before" one. + * + * @see IResourceDelta#getKind() + */ + public static final int ADDED = 0x1; + + /** + * Delta kind constant (bit mask) indicating that the resource has been removed + * from its parent. That is, one that appears in the "before" state, + * not in the "after" one. + * + * @see IResourceDelta#getKind() + */ + public static final int REMOVED = 0x2; + + /** + * Delta kind constant (bit mask) indicating that the resource has been changed. + * That is, one that appears in both the "before" and "after" states. + * + * @see IResourceDelta#getKind() + */ + public static final int CHANGED = 0x4; + + /** + * Delta kind constant (bit mask) indicating that a phantom resource has been added at + * the location of the delta node. + * + * @see IResourceDelta#getKind() + */ + public static final int ADDED_PHANTOM = 0x8; + + /** + * Delta kind constant (bit mask) indicating that a phantom resource has been removed from + * the location of the delta node. + * + * @see IResourceDelta#getKind() + */ + public static final int REMOVED_PHANTOM = 0x10; + + /** + * The bit mask which describes all possible delta kinds, + * including ones involving phantoms. + * + * @see IResourceDelta#getKind() + */ + public static final int ALL_WITH_PHANTOMS = CHANGED | ADDED | REMOVED | ADDED_PHANTOM | REMOVED_PHANTOM; + + /*==================================================================== + * Constants which describe resource changes: + *====================================================================*/ + + /** + * Change constant (bit mask) indicating that the content of the resource has changed. + * + * @see IResourceDelta#getFlags() + */ + public static final int CONTENT = 0x100; + + /** + * Change constant (bit mask) indicating that the resource was moved from another location. + * The location in the "before" state can be retrieved using getMovedFromPath(). + * + * @see IResourceDelta#getFlags() + */ + public static final int MOVED_FROM = 0x1000; + + /** + * Change constant (bit mask) indicating that the resource was moved to another location. + * The location in the new state can be retrieved using getMovedToPath(). + * + * @see IResourceDelta#getFlags() + */ + public static final int MOVED_TO = 0x2000; + + /** + * Change constant (bit mask) indicating that the resource was copied from another location. + * The location in the "before" state can be retrieved using getMovedFromPath(). + * This flag is only used when describing potential changes using an {@link IResourceChangeDescriptionFactory}. + * + * @see IResourceDelta#getFlags() + * @since 3.2 + */ + public static final int COPIED_FROM = 0x800; + /** + * Change constant (bit mask) indicating that the resource was opened or closed. + * This flag is also set when the project did not exist in the "before" state. + * For example, if the current state of the resource is open then it was previously closed + * or did not exist. + * + * @see IResourceDelta#getFlags() + */ + public static final int OPEN = 0x4000; + + /** + * Change constant (bit mask) indicating that the type of the resource has changed. + * + * @see IResourceDelta#getFlags() + */ + public static final int TYPE = 0x8000; + + /** + * Change constant (bit mask) indicating that the resource's sync status has changed. + * This type of change is not included in build deltas, only in those for resource notification. + * + * @see IResourceDelta#getFlags() + */ + public static final int SYNC = 0x10000; + + /** + * Change constant (bit mask) indicating that the resource's markers have changed. + * This type of change is not included in build deltas, only in those for resource notification. + * + * @see IResourceDelta#getFlags() + */ + public static final int MARKERS = 0x20000; + + /** + * Change constant (bit mask) indicating that the resource has been + * replaced by another at the same location (i.e., the resource has + * been deleted and then added). + * + * @see IResourceDelta#getFlags() + */ + public static final int REPLACED = 0x40000; + + /** + * Change constant (bit mask) indicating that a project's description has changed. + * + * @see IResourceDelta#getFlags() + */ + public static final int DESCRIPTION = 0x80000; + + /** + * Change constant (bit mask) indicating that the encoding of the resource has changed. + * + * @see IResourceDelta#getFlags() + * @since 3.0 + */ + public static final int ENCODING = 0x100000; + + /** + * Change constant (bit mask) indicating that the underlying file or folder of the linked resource has been added or removed. + * + * @see IResourceDelta#getFlags() + * @since 3.4 + */ + public static final int LOCAL_CHANGED = 0x200000; + + /** + * Change constant (bit mask) indicating that the derived flag of the resource has changed. + * + * @see IResourceDelta#getFlags() + * @since 3.6 + */ + public static final int DERIVED_CHANGED = 0x400000; + + /** + * Accepts the given visitor. + * The only kinds of resource deltas visited + * are ADDED, REMOVED, + * and CHANGED. + * The visitor's visit method is called with this + * resource delta if applicable. If the visitor returns true, + * the resource delta's children are also visited. + *

                      + * This is a convenience method, fully equivalent to + * accept(visitor, IResource.NONE). + * Although the visitor will be invoked for this resource delta, it will not be + * invoked for any team-private member resources. + *

                      + * + * @param visitor the visitor + * @exception CoreException if the visitor failed with this exception. + * @see IResourceDeltaVisitor#visit(IResourceDelta) + */ + public void accept(IResourceDeltaVisitor visitor) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource delta. If the visitor returns true, + * the resource delta's children are also visited. + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   accept(visitor, includePhantoms ? INCLUDE_PHANTOMS : IResource.NONE);
                      +	 * 
                      + * Although the visitor will be invoked for this resource delta, it will not be + * invoked for any team-private member resources. + *

                      + * + * @param visitor the visitor + * @param includePhantoms true if phantom resources are + * of interest; false if phantom resources are not of + * interest + * @exception CoreException if the visitor failed with this exception. + * @see #accept(IResourceDeltaVisitor) + * @see IResource#isPhantom() + * @see IResourceDeltaVisitor#visit(IResourceDelta) + */ + public void accept(IResourceDeltaVisitor visitor, boolean includePhantoms) throws CoreException; + + /** + * Accepts the given visitor. + * The visitor's visit method is called with this + * resource delta. If the visitor returns true, + * the resource delta's children are also visited. + *

                      + * The member flags determine which child deltas of this resource delta will be visited. + * The visitor will always be invoked for this resource delta. + *

                      + * If the INCLUDE_PHANTOMS member flag is not specified + * (recommended), only child resource deltas involving existing resources will be visited + * (kinds ADDED, REMOVED, and CHANGED). + * If the INCLUDE_PHANTOMS member flag is specified, + * the result will also include additions and removes of phantom resources + * (kinds ADDED_PHANTOM and REMOVED_PHANTOM). + *

                      + *

                      + * If the INCLUDE_TEAM_PRIVATE_MEMBERS member flag is not specified + * (recommended), resource deltas involving team private member resources will be + * excluded from the visit. If the INCLUDE_TEAM_PRIVATE_MEMBERS member + * flag is specified, the visit will also include additions and removes of + * team private member resources. + *

                      + * + * @param visitor the visitor + * @param memberFlags bit-wise or of member flag constants + * (IContainer.INCLUDE_PHANTOMS, INCLUDE_HIDDEN + * and INCLUDE_TEAM_PRIVATE_MEMBERS) indicating which members are of interest + * @exception CoreException if the visitor failed with this exception. + * @see IResource#isPhantom() + * @see IResource#isTeamPrivateMember() + * @see IResource#isHidden() + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @see IResourceDeltaVisitor#visit(IResourceDelta) + * @since 2.0 + */ + public void accept(IResourceDeltaVisitor visitor, int memberFlags) throws CoreException; + + /** + * Finds and returns the descendent delta identified by the given path in + * this delta, or null if no such descendent exists. + * The supplied path may be absolute or relative; in either case, it is + * interpreted as relative to this delta. Trailing separators are ignored. + * If the path is empty this delta is returned. + *

                      + * This is a convenience method to avoid manual traversal of the delta + * tree in cases where the listener is only interested in changes to + * particular resources. Calling this method will generally be + * faster than manually traversing the delta to a particular descendent. + *

                      + * @param path the path of the desired descendent delta + * @return the descendent delta, or null if no such + * descendent exists in the delta + * @since 2.0 + */ + public IResourceDelta findMember(IPath path); + + /** + * Returns resource deltas for all children of this resource + * which were added, removed, or changed. Returns an empty + * array if there are no affected children. + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   getAffectedChildren(ADDED | REMOVED | CHANGED, IResource.NONE);
                      +	 * 
                      + * Team-private member resources are not included in the result; neither are + * phantom resources. + *

                      + * + * @return the resource deltas for all affected children + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see #getAffectedChildren(int,int) + */ + public IResourceDelta[] getAffectedChildren(); + + /** + * Returns resource deltas for all children of this resource + * whose kind is included in the given mask. Kind masks are formed + * by the bitwise or of IResourceDelta kind constants. + * Returns an empty array if there are no affected children. + *

                      + * This is a convenience method, fully equivalent to: + *

                      +	 *   getAffectedChildren(kindMask, IResource.NONE);
                      +	 * 
                      + * Team-private member resources are not included in the result. + *

                      + * + * @param kindMask a mask formed by the bitwise or of IResourceDelta + * delta kind constants + * @return the resource deltas for all affected children + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see IResourceDelta#ADDED_PHANTOM + * @see IResourceDelta#REMOVED_PHANTOM + * @see IResourceDelta#ALL_WITH_PHANTOMS + * @see #getAffectedChildren(int,int) + */ + public IResourceDelta[] getAffectedChildren(int kindMask); + + /** + * Returns resource deltas for all children of this resource + * whose kind is included in the given mask. Masks are formed + * by the bitwise or of IResourceDelta kind constants. + * Returns an empty array if there are no affected children. + *

                      + * If the INCLUDE_TEAM_PRIVATE_MEMBERS member flag is not specified, + * (recommended), resource deltas involving team private member resources will be + * excluded. If the INCLUDE_TEAM_PRIVATE_MEMBERS member + * flag is specified, the result will also include resource deltas of the + * specified kinds to team private member resources. + *

                      + *

                      + * If the {@link IContainer#INCLUDE_HIDDEN} member flag is not specified, + * (recommended), resource deltas involving hidden resources will be + * excluded. If the {@link IContainer#INCLUDE_HIDDEN} member + * flag is specified, the result will also include resource deltas of the + * specified kinds to hidden resources. + *

                      + *

                      + * Specifying the IContainer.INCLUDE_PHANTOMS member flag is equivalent + * to including IContainer.ADDED_PHANTOM and IContainer.REMOVED_PHANTOM + * in the kind mask. + *

                      + * + * @param kindMask a mask formed by the bitwise or of IResourceDelta + * delta kind constants + * @param memberFlags bit-wise or of member flag constants + * (IContainer.INCLUDE_PHANTOMS, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS + * and IContainer.INCLUDE_HIDDEN) + * indicating which members are of interest + * @return the resource deltas for all affected children + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see IResourceDelta#ADDED_PHANTOM + * @see IResourceDelta#REMOVED_PHANTOM + * @see IResourceDelta#ALL_WITH_PHANTOMS + * @see IContainer#INCLUDE_PHANTOMS + * @see IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS + * @see IContainer#INCLUDE_HIDDEN + * @since 2.0 + */ + public IResourceDelta[] getAffectedChildren(int kindMask, int memberFlags); + + /** + * Returns flags which describe in more detail how a resource has been affected. + *

                      + * The following codes (bit masks) are used when kind is CHANGED, and + * also when the resource is involved in a move: + *

                        + *
                      • CONTENT - The bytes contained by the resource have + * been altered, or IResource.touch has been called on + * the resource.
                      • + *
                      • DERIVED_CHANGED - The derived flag of the resource has + * been altered.
                      • + *
                      • ENCODING - The encoding of the resource may have been altered. + * This flag is not set when the encoding changes due to the file being modified, + * or being moved.
                      • + *
                      • DESCRIPTION - The description of the project has been altered, + * or IResource.touch has been called on the project. + * This flag is only valid for project resources.
                      • + *
                      • OPEN - The project's open/closed state has changed. + * If it is not open, it was closed, and vice versa. This flag is only valid for project resources.
                      • + *
                      • TYPE - The resource (a folder or file) has changed its type.
                      • + *
                      • SYNC - The resource's sync status has changed.
                      • + *
                      • MARKERS - The resource's markers have changed.
                      • + *
                      • REPLACED - The resource (and all its properties) + * was deleted (either by a delete or move), and was subsequently re-created + * (either by a create, move, or copy).
                      • + *
                      • LOCAL_CHANGED - The resource is a linked resource, + * and the underlying file system object has been added or removed.
                      • + *
                      + * The following code is only used if kind is REMOVED + * (or CHANGED in conjunction with REPLACED): + *
                        + *
                      • MOVED_TO - The resource has moved. + * getMovedToPath will return the path of where it was moved to.
                      • + *
                      + * The following code is only used if kind is ADDED + * (or CHANGED in conjunction with REPLACED): + *
                        + *
                      • MOVED_FROM - The resource has moved. + * getMovedFromPath will return the path of where it was moved from.
                      • + *
                      + * The following code is only used when describing potential changes using an {@link IResourceChangeDescriptionFactory}: + *
                        + *
                      • COPIED_FROM - Change constant (bit mask) indicating that the resource was copied from another location. + * The location in the "before" state can be retrieved using getMovedFromPath().
                      • + *
                      + * + * A simple move operation would result in the following delta information. + * If a resource is moved from A to B (with no other changes to A or B), + * then A will have kind REMOVED, with flag MOVED_TO, + * and getMovedToPath on A will return the path for B. + * B will have kind ADDED, with flag MOVED_FROM, + * and getMovedFromPath on B will return the path for A. + * B's other flags will describe any other changes to the resource, as compared + * to its previous location at A. + *

                      + *

                      + * Note that the move flags only describe the changes to a single resource; they + * don't necessarily imply anything about the parent or children of the resource. + * If the children were moved as a consequence of a subtree move operation, + * they will have corresponding move flags as well. + *

                      + *

                      + * Note that it is possible for a file resource to be replaced in the workspace + * by a folder resource (or the other way around). + * The resource delta, which is actually expressed in terms of + * paths instead or resources, shows this as a change to either the + * content or children. + *

                      + * + * @return the flags + * @see IResourceDelta#CONTENT + * @see IResourceDelta#DERIVED_CHANGED + * @see IResourceDelta#DESCRIPTION + * @see IResourceDelta#ENCODING + * @see IResourceDelta#LOCAL_CHANGED + * @see IResourceDelta#OPEN + * @see IResourceDelta#MOVED_TO + * @see IResourceDelta#MOVED_FROM + * @see IResourceDelta#COPIED_FROM + * @see IResourceDelta#TYPE + * @see IResourceDelta#SYNC + * @see IResourceDelta#MARKERS + * @see IResourceDelta#REPLACED + * @see #getKind() + * @see #getMovedFromPath() + * @see #getMovedToPath() + * @see IResource#move(IPath, int, IProgressMonitor) + */ + public int getFlags(); + + /** + * Returns the full, absolute path of this resource delta. + *

                      + * Note: the returned path never has a trailing separator. + *

                      + * @return the full, absolute path of this resource delta + * @see IResource#getFullPath() + * @see #getProjectRelativePath() + */ + public IPath getFullPath(); + + /** + * Returns the kind of this resource delta. + * Normally, one of ADDED, + * REMOVED, CHANGED. + * When phantom resources have been explicitly requested, + * there are two additional kinds: ADDED_PHANTOM + * and REMOVED_PHANTOM. + * + * @return the kind of this resource delta + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see IResourceDelta#ADDED_PHANTOM + * @see IResourceDelta#REMOVED_PHANTOM + */ + public int getKind(); + + /** + * Returns the changes to markers on the corresponding resource. + * Returns an empty array if no markers changed. + * + * @return the marker deltas + */ + public IMarkerDelta[] getMarkerDeltas(); + + /** + * Returns the full path (in the "before" state) from which this resource + * (in the "after" state) was moved. This value is only valid + * if the MOVED_FROM change flag is set; otherwise, + * null is returned. + *

                      + * Note: the returned path never has a trailing separator. + * + * @return a path, or null + * @see #getMovedToPath() + * @see #getFullPath() + * @see #getFlags() + */ + public IPath getMovedFromPath(); + + /** + * Returns the full path (in the "after" state) to which this resource + * (in the "before" state) was moved. This value is only valid if the + * MOVED_TO change flag is set; otherwise, + * null is returned. + *

                      + * Note: the returned path never has a trailing separator. + * + * @return a path, or null + * @see #getMovedFromPath() + * @see #getFullPath() + * @see #getFlags() + */ + public IPath getMovedToPath(); + + /** + * Returns the project-relative path of this resource delta. + * Returns the empty path for projects and the workspace root. + *

                      + * A resource's project-relative path indicates the route from the project + * to the resource. Within a workspace, there is exactly one such path + * for any given resource. The returned path never has a trailing separator. + *

                      + * @return the project-relative path of this resource delta + * @see IResource#getProjectRelativePath() + * @see #getFullPath() + * @see Path#EMPTY + */ + public IPath getProjectRelativePath(); + + /** + * Returns a handle for the affected resource. + *

                      + * For additions (ADDED), this handle describes the newly-added resource; i.e., + * the one in the "after" state. + *

                      + * For changes (CHANGED), this handle also describes the resource in the "after" + * state. When a file or folder resource has changed type, the + * former type of the handle can be inferred. + *

                      + * For removals (REMOVED), this handle describes the resource in the "before" + * state. Even though this resource would not normally exist in the + * current workspace, the type of resource that was removed can be + * determined from the handle. + *

                      + * For phantom additions and removals (ADDED_PHANTOM + * and REMOVED_PHANTOM), this is the handle of the phantom resource. + * + * @return the affected resource (handle) + */ + public IResource getResource(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java new file mode 100644 index 0000000000..c1824355d5 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * An objects that visits resource deltas. + *

                      + * Usage: + *

                      + * class Visitor implements IResourceDeltaVisitor {
                      + *     public boolean visit(IResourceDelta delta) {
                      + *         switch (delta.getKind()) {
                      + *         case IResourceDelta.ADDED :
                      + *             // handle added resource
                      + *             break;
                      + *         case IResourceDelta.REMOVED :
                      + *             // handle removed resource
                      + *             break;
                      + *         case IResourceDelta.CHANGED :
                      + *             // handle changed resource
                      + *             break;
                      + *         }
                      + *     return true;
                      + *     }
                      + * }
                      + * IResourceDelta rootDelta = ...;
                      + * rootDelta.accept(new Visitor());
                      + * 
                      + *

                      + *

                      + * Clients may implement this interface. + *

                      + * + * @see IResource#accept(IResourceVisitor) + */ +public interface IResourceDeltaVisitor { + /** + * Visits the given resource delta. + * + * @return true if the resource delta's children should + * be visited; false if they should be skipped. + * @exception CoreException if the visit fails for some reason. + */ + public boolean visit(IResourceDelta delta) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java new file mode 100644 index 0000000000..669142b3a8 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceFilterDescription.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 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 - ongoing development + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.runtime.*; + +/** + * A description of a resource filter. + * + * A filter determines which file system objects will be visible when a local refresh is + * performed for an IContainer. + * + * @see IContainer#getFilters() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * @since 3.6 + */ +public interface IResourceFilterDescription { + + /*==================================================================== + * Constants defining which members are wanted: + *====================================================================*/ + + /** + * Flag for resource filters indicating that the filter list includes only + * the files matching the filters. All INCLUDE_ONLY filters are applied to + * the resource list with an logical OR operation. + */ + public static final int INCLUDE_ONLY = 1; + + /** + * Flag for resource filters indicating that the filter list excludes all + * the files matching the filters. All EXCLUDE_ALL filters are applied to + * the resource list with an logical AND operation. + */ + public static final int EXCLUDE_ALL = 2; + + /** + * Flag for resource filters indicating that this filter applies to files. + */ + public static final int FILES = 4; + + /** + * Flag for resource filters indicating that this filter applies to folders. + */ + public static final int FOLDERS = 8; + + /** + * Flag for resource filters indicating that the container children of the + * path inherit from this filter as well. + */ + public static final int INHERITABLE = 16; + + /** + * Returns the description of the file info matcher corresponding to this resource + * filter. + * @return the file info matcher description for this resource filter + */ + public FileInfoMatcherDescription getFileInfoMatcherDescription(); + + /** + * Return the resource towards which this filter is set. + * + * @return the resource towards which this filter is set + */ + public IResource getResource(); + + /** + * Return the filter type, either INCLUDE_ONLY or EXCLUDE_ALL + * + * @return (INCLUDE_ONLY or EXCLUDE_ALL) and/or INHERITABLE + */ + public int getType(); + + /** + * Deletes this filter description from its associated resource. + *

                      + * The {@link IResource#BACKGROUND_REFRESH} update flag controls when + * changes to the resource hierarchy under this container resulting from the filter + * removal take effect. If this flag is specified, the resource hierarchy is updated in a + * separate thread after this method returns. If the flag is not specified, any resource + * changes resulting from the filter removal will occur before this method returns. + *

                      + *

                      + * This operation changes resources; these changes will be reported + * in a subsequent resource change event that will include an indication + * of any resources that have been added as a result of the filter removal. + *

                      + *

                      + * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                      + * + * @param updateFlags bit-wise or of update flag constants + * ({@link IResource#BACKGROUND_REFRESH}) + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this filter could not be removed. Reasons include: + *
                        + *
                      • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IContainer#getFilters() + * @see IContainer#createFilter(int, FileInfoMatcherDescription, int, IProgressMonitor) + */ + public void delete(int updateFlags, IProgressMonitor monitor) throws CoreException; + +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java new file mode 100644 index 0000000000..28939816a4 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.QualifiedName; + +/** + * A lightweight interface for requesting information about a resource. + * All of the "get" methods on a resource proxy have trivial performance cost. + * Requesting the full path or the actual resource handle will cause extra objects + * to be created and will thus have greater cost. + *

                      + * When a resource proxy is used within an {@link IResourceProxyVisitor}, + * it is a transient object that is only valid for the duration of a single visit method. + * A proxy should not be referenced once the single resource visit is complete. + * The equals and hashCode methods should not be relied on. + *

                      + *

                      + * A proxy can also be created using {@link IResource#createProxy()}. In + * this case the proxy is valid indefinitely, but will not remain in sync with + * the state of the corresponding resource. + *

                      + * + * @see IResourceProxyVisitor + * @since 2.1 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceProxy { + /** + * Returns the modification stamp of the resource being visited. + * + * @return the modification stamp, or NULL_STAMP if the + * resource either does not exist or exists as a closed project + * @see IResource#getModificationStamp() + */ + public long getModificationStamp(); + + /** + * Returns whether the resource being visited is accessible. + * + * @return true if the resource is accessible, and + * false otherwise + * @see IResource#isAccessible() + */ + public boolean isAccessible(); + + /** + * Returns whether the resource being visited is derived. + * + * @return true if the resource is marked as derived, and + * false otherwise + * @see IResource#isDerived() + */ + public boolean isDerived(); + + /** + * Returns whether the resource being visited is a linked resource. + * + * @return true if the resource is linked, and + * false otherwise + * @see IResource#isLinked() + */ + public boolean isLinked(); + + /** + * Returns whether the resource being visited is a phantom resource. + * + * @return true if the resource is a phantom resource, and + * false otherwise + * @see IResource#isPhantom() + */ + public boolean isPhantom(); + + /** + * Returns whether the resource being visited is a hidden resource. + * + * @return true if the resource is a hidden resource, and + * false otherwise + * @see IResource#isHidden() + * + * @since 3.4 + */ + public boolean isHidden(); + + /** + * Returns whether the resource being visited is a team private member. + * + * @return true if the resource is a team private member, and + * false otherwise + * @see IResource#isTeamPrivateMember() + */ + public boolean isTeamPrivateMember(); + + /** + * Returns the simple name of the resource being visited. + * + * @return the name of the resource + * @see IResource#getName() + */ + public String getName(); + + /** + * Returns the value of the session property of the resource being + * visited, identified by the given key. Returns null if this + * resource has no such property. + *

                      + * Note that this method can return an out of date property value, or a + * value that no longer exists, if session properties are being modified + * concurrently with the resource visit. + *

                      + * + * @param key the qualified name of the property + * @return the string value of the session property, + * or null if the resource has no such property + * @see IResource#getSessionProperty(QualifiedName) + */ + public Object getSessionProperty(QualifiedName key); + + /** + * Returns the type of the resource being visited. + * + * @return the resource type + * @see IResource#getType() + */ + public int getType(); + + /** + * Returns the full workspace path of the resource being visited. + *

                      + * Note that this is not a "free" proxy operation. This method + * will generally cause a path object to be created. For an optimal + * visitor, only call this method when absolutely necessary. Note that the + * simple resource name can be obtained from the proxy with no cost. + *

                      + * @return the full path of the resource + * @see IResource#getFullPath() + */ + public IPath requestFullPath(); + + /** + * Returns the handle of the resource being visited. + *

                      + * Note that this is not a "free" proxy operation. This method will + * generally cause both a path object and a resource object to be created. + * For an optimal visitor, only call this method when absolutely necessary. + * Note that the simple resource name can be obtained from the proxy with no + * cost, and the full path of the resource can be obtained through the proxy + * with smaller cost. + *

                      + * @return the resource handle + */ + public IResource requestResource(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java new file mode 100644 index 0000000000..583b72cbb4 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * This interface is implemented by objects that visit resource trees. The fast + * visitor is an optimized mechanism for tree traversal that creates a minimal + * number of objects. The visitor is provided with a callback interface, + * instead of a resource. Through the callback, the visitor can request + * information about the resource being visited. + *

                      + * Usage: + *

                      + * class Visitor implements IResourceProxyVisitor {
                      + * 	public boolean visit (IResourceProxy proxy) {
                      + * 		//	 your code here
                      + * 		return true;
                      + * 	}
                      + * }
                      + * ResourcesPlugin.getWorkspace().getRoot().accept(new Visitor(), IResource.NONE);
                      + * 
                      + *

                      + *

                      + * Clients may implement this interface. + *

                      + * + * @see IResource#accept(IResourceVisitor) + * @since 2.1 + */ +public interface IResourceProxyVisitor { + /** + * Visits the given resource. + * + * @param proxy for requesting information about the resource being visited; + * this object is only valid for the duration of the invocation of this + * method, and must not be used after this method has completed + * @return true if the resource's members should + * be visited; false if they should be skipped + * @exception CoreException if the visit fails for some reason. + */ + public boolean visit(IResourceProxy proxy) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java new file mode 100644 index 0000000000..149ee99b3b --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * 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 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.runtime.ICoreRunnable; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * A resource rule factory returns scheduling rules for API methods + * that modify the workspace. These rules can be used when creating jobs + * or other operations that perform a series of modifications on the workspace. + * This allows clients to implement two phase commit semantics, where all + * necessary rules are obtained prior to executing a long running operation. + *

                      + * Note that simple use of the workspace APIs does not require use of scheduling + * rules. All workspace API methods that modify the workspace will automatically + * obtain any scheduling rules needed to perform the modification. However, if you + * are aggregating a set of changes to the workspace using WorkspaceJob + * or ICoreRunnable you can use scheduling rules to lock a + * portion of the workspace for the duration of the job or runnable. If you + * provide a non-null scheduling rule, a runtime exception will occur if you try to + * modify a portion of the workspace that is not covered by the rule for the runnable or job. + *

                      + * If more than one rule is needed, they can be aggregated using the + * MultiRule.combine method. Simplifying a group of rules does not change + * the set of resources that are covered, but can improve job scheduling performance. + *

                      + * Note that null is a valid scheduling rule (indicating that no + * resources need to be locked), and thus all methods in this class may + * return null. + * + * @see WorkspaceJob + * @see IWorkspace#run(ICoreRunnable, ISchedulingRule, int, org.eclipse.core.runtime.IProgressMonitor) + * @see org.eclipse.core.runtime.jobs.MultiRule#combine(ISchedulingRule, ISchedulingRule) + * @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 IResourceRuleFactory { + /** + * Returns the scheduling rule that is required for creating a project, folder, + * or file. + * + * @param resource the resource being created + * @return a scheduling rule, or null + */ + public ISchedulingRule createRule(IResource resource); + + /** + * Returns the scheduling rule that is required for building a project or the + * entire workspace. + * + * @return a scheduling rule, or null + */ + public ISchedulingRule buildRule(); + + /** + * Returns the scheduling rule that is required for changing the charset + * setting for a file or the default charset setting for a container. + * + * @param resource the resource for which the charset will be changed + * @return a scheduling rule, or null + * @since 3.1 + */ + public ISchedulingRule charsetRule(IResource resource); + + /** + * Returns the scheduling rule that is required for changing the derived flag + * on a resource. + * + * @param resource the resource for which the derived flag will be changed + * @return a scheduling rule, or null + * @since 3.6 + */ + public ISchedulingRule derivedRule(IResource resource); + + /** + * Returns the scheduling rule that is required for copying a resource. + * + * @param source the source of the copy + * @param destination the destination of the copy + * @return a scheduling rule, or null + */ + public ISchedulingRule copyRule(IResource source, IResource destination); + + /** + * Returns the scheduling rule that is required for deleting a resource. + * + * @param resource the resource to be deleted + * @return a scheduling rule, or null + */ + public ISchedulingRule deleteRule(IResource resource); + + /** + * Returns the scheduling rule that is required for creating, modifying, or + * deleting markers on a resource. + * + * @param resource the resource owning the marker to be modified + * @return a scheduling rule, or null + */ + public ISchedulingRule markerRule(IResource resource); + + /** + * Returns the scheduling rule that is required for maintaining sync + * info of a resource. + * + * @see ISynchronizer#setSyncInfo(QualifiedName, IResource, byte[]) + * @param resource the resource related to the sync info + * @return a scheduling rule, or null + * @since 3.12.100 + */ + public ISchedulingRule syncInfoRule(IResource resource); + + /** + * Returns the scheduling rule that is required for modifying a resource. + * For files, modification includes setting and appending contents. For + * projects, modification includes opening or closing the project, or + * setting the project description using the + * {@link IResource#AVOID_NATURE_CONFIG} flag. For all resources + * touch is considered to be a modification. + * + * @param resource the resource being modified + * @return a scheduling rule, or null + */ + public ISchedulingRule modifyRule(IResource resource); + + /** + * Returns the scheduling rule that is required for moving a resource. + * + * @param source the source of the move + * @param destination the destination of the move + * @return a scheduling rule, or null + */ + public ISchedulingRule moveRule(IResource source, IResource destination); + + /** + * Returns the scheduling rule that is required for performing + * refreshLocal on a resource. + * + * @param resource the resource to refresh + * @return a scheduling rule, or null + */ + public ISchedulingRule refreshRule(IResource resource); + + /** + * Returns the scheduling rule that is required for a validateEdit + * + * @param resources the resources to be validated + * @return a scheduling rule, or null + */ + public ISchedulingRule validateEditRule(IResource[] resources); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java new file mode 100644 index 0000000000..1ec95112ed --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java @@ -0,0 +1,322 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.runtime.*; + +/** + * Represents status related to resources in the Resources plug-in and + * defines the relevant status code constants. + * Status objects created by the Resources plug-in bear its unique id + * (ResourcesPlugin.PI_RESOURCES) and one of + * these status codes. + * + * @see org.eclipse.core.runtime.IStatus + * @see ResourcesPlugin#PI_RESOURCES + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceStatus extends IStatus { + + /* + * Status code definitions + */ + + // General constants [0-98] + // Information Only [0-32] + // Warnings [33-65] + /** Status code constant (value 35) indicating that a given + * nature set does not satisfy its constraints. + * Severity: warning. Category: general. + */ + public static final int INVALID_NATURE_SET = 35; + + // Errors [66-98] + + /** Status code constant (value 75) indicating that a builder failed. + * Severity: error. Category: general. + */ + public static final int BUILD_FAILED = 75; + + /** Status code constant (value 76) indicating that an operation failed. + * Severity: error. Category: general. + */ + public static final int OPERATION_FAILED = 76; + + /** Status code constant (value 77) indicating an invalid value. + * Severity: error. Category: general. + */ + public static final int INVALID_VALUE = 77; + + // Local file system constants [200-298] + // Information Only [200-232] + + // Warnings [233-265] + + /** Status code constant (value 234) indicating that a project + * description file (.project), was missing but it has been repaired. + * Severity: warning. Category: local file system. + */ + public static final int MISSING_DESCRIPTION_REPAIRED = 234; + + /** Status code constant (value 235) indicating the local file system location + * for a resource overlaps the location of another resource. + * Severity: warning. Category: local file system. + */ + public static final int OVERLAPPING_LOCATION = 235; + + // Errors [266-298] + + /** Status code constant (value 268) indicating a resource unexpectedly + * exists on the local file system. + * Severity: error. Category: local file system. + */ + public static final int EXISTS_LOCAL = 268; + + /** Status code constant (value 269) indicating a resource unexpectedly + * does not exist on the local file system. + * Severity: error. Category: local file system. + */ + public static final int NOT_FOUND_LOCAL = 269; + + /** Status code constant (value 270) indicating the local file system location for + * a resource could not be computed. + * Severity: error. Category: local file system. + */ + public static final int NO_LOCATION_LOCAL = 270; + + /** Status code constant (value 271) indicating an error occurred while + * reading part of a resource from the local file system. + * Severity: error. Category: local file system. + */ + public static final int FAILED_READ_LOCAL = 271; + + /** Status code constant (value 272) indicating an error occurred while + * writing part of a resource to the local file system. + * Severity: error. Category: local file system. + */ + public static final int FAILED_WRITE_LOCAL = 272; + + /** Status code constant (value 273) indicating an error occurred while + * deleting a resource from the local file system. + * Severity: error. Category: local file system. + */ + public static final int FAILED_DELETE_LOCAL = 273; + + /** Status code constant (value 274) indicating the workspace view of + * the resource differs from that of the local file system. The requested + * operation has been aborted to prevent the possible loss of data. + * Severity: error. Category: local file system. + */ + public static final int OUT_OF_SYNC_LOCAL = 274; + + /** Status code constant (value 275) indicating this file system is not case + * sensitive and a resource that differs only in case unexpectedly exists on + * the local file system. + * Severity: error. Category: local file system. + */ + public static final int CASE_VARIANT_EXISTS = 275; + + /** Status code constant (value 276) indicating a file exists in the + * file system but is not of the expected type (file instead of directory, + * or vice-versa). + * Severity: error. Category: local file system. + */ + public static final int WRONG_TYPE_LOCAL = 276; + + /** Status code constant (value 277) indicating that the parent + * file in the file system is marked as read-only. + * Severity: error. Category: local file system. + * @since 2.1 + */ + public static final int PARENT_READ_ONLY = 277; + + /** Status code constant (value 278) indicating a file exists in the + * file system but its name is not a valid resource name. + * Severity: error. Category: local file system. + */ + public static final int INVALID_RESOURCE_NAME = 278; + + /** Status code constant (value 279) indicating that the + * file in the file system is marked as read-only. + * Severity: error. Category: local file system. + * @since 3.0 + */ + public static final int READ_ONLY_LOCAL = 279; + + // Workspace constants [300-398] + // Information Only [300-332] + + // Warnings [333-365] + + /** Status code constant (value 333) indicating that a workspace path + * variable unexpectedly does not exist. + * Severity: warning. Category: workspace. + * @since 2.1 + */ + public static final int VARIABLE_NOT_DEFINED_WARNING = 333; + + // Errors [366-398] + + /** Status code constant (value 366) indicating a resource exists in the + * workspace but is not of the expected type. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_WRONG_TYPE = 366; + + /** Status code constant (value 367) indicating a resource unexpectedly + * exists in the workspace. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_EXISTS = 367; + + /** Status code constant (value 368) indicating a resource unexpectedly + * does not exist in the workspace. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_NOT_FOUND = 368; + + /** Status code constant (value 369) indicating a resource unexpectedly + * does not have content local to the workspace. + * Severity: error. Category: workspace. + */ + public static final int RESOURCE_NOT_LOCAL = 369; + + /** Status code constant (value 370) indicating a workspace + * is unexpectedly closed. + * Severity: error. Category: workspace. + */ + public static final int WORKSPACE_NOT_OPEN = 370; + + /** Status code constant (value 372) indicating a project is + * unexpectedly closed. + * Severity: error. Category: workspace. + */ + public static final int PROJECT_NOT_OPEN = 372; + + /** Status code constant (value 374) indicating that the path + * of a resource being created is occupied by an existing resource + * of a different type. + * Severity: error. Category: workspace. + */ + public static final int PATH_OCCUPIED = 374; + + /** Status code constant (value 375) indicating that the sync partner + * is not registered with the workspace synchronizer. + * Severity: error. Category: workspace. + */ + public static final int PARTNER_NOT_REGISTERED = 375; + + /** Status code constant (value 376) indicating a marker unexpectedly + * does not exist in the workspace tree. + * Severity: error. Category: workspace. + */ + public static final int MARKER_NOT_FOUND = 376; + + /** Status code constant (value 377) indicating a resource is + * unexpectedly not a linked resource. + * Severity: error. Category: workspace. + * @since 2.1 + */ + public static final int RESOURCE_NOT_LINKED = 377; + + /** Status code constant (value 378) indicating that linking is + * not permitted on a certain project. + * Severity: error. Category: workspace. + * @since 2.1 + */ + public static final int LINKING_NOT_ALLOWED = 378; + + /** Status code constant (value 379) indicating that a workspace path + * variable unexpectedly does not exist. + * Severity: error. Category: workspace. + * @since 2.1 + */ + public static final int VARIABLE_NOT_DEFINED = 379; + + /** Status code constant (value 380) indicating that an attempt was made to modify + * the workspace while it was locked. Resource changes are disallowed + * during certain types of resource change event notification. + * Severity: error. Category: workspace. + * @see IResourceChangeEvent + * @since 2.1 + */ + public static final int WORKSPACE_LOCKED = 380; + + /** Status code constant (value 381) indicating that a problem occurred while + * retrieving the content description for a resource. + * Severity: error. Category: workspace. + * @see IFile#getContentDescription + * @since 3.0 + */ + public static final int FAILED_DESCRIBING_CONTENTS = 381; + + /** Status code constant (value 382) indicating that a problem occurred while + * setting the charset for a resource. + * Severity: error. Category: workspace. + * @see IContainer#setDefaultCharset(String, IProgressMonitor) + * @see IFile#setCharset(String, IProgressMonitor) + * @since 3.0 + */ + public static final int FAILED_SETTING_CHARSET = 382; + + /** Status code constant (value 383) indicating that a problem occurred while + * getting the charset for a resource. + * Severity: error. Category: workspace. + * @since 3.0 + */ + public static final int FAILED_GETTING_CHARSET = 383; + + /** Status code constant (value 384) indicating a build configuration with + * the specified ID unexpectedly does not exist. + * Severity: error. Category: workspace. + * @since 3.7 + */ + public static final int BUILD_CONFIGURATION_NOT_FOUND = 384; + + // Internal constants [500-598] + // Information Only [500-532] + + // Warnings [533-565] + + // Errors [566-598] + + /** Status code constant (value 566) indicating an error internal to the + * platform has occurred. + * Severity: error. Category: internal. + */ + public static final int INTERNAL_ERROR = 566; + + /** Status code constant (value 567) indicating the platform could not read + * some of its metadata. + * Severity: error. Category: internal. + */ + public static final int FAILED_READ_METADATA = 567; + + /** Status code constant (value 568) indicating the platform could not write + * some of its metadata. + * Severity: error. Category: internal. + */ + public static final int FAILED_WRITE_METADATA = 568; + + /** Status code constant (value 569) indicating the platform could not delete + * some of its metadata. + * Severity: error. Category: internal. + */ + public static final int FAILED_DELETE_METADATA = 569; + + /** + * Returns the path of the resource associated with this status. + * + * @return the path of the resource related to this status + */ + public IPath getPath(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java new file mode 100644 index 0000000000..ee43d864e0 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; + +/** + * This interface is implemented by objects that visit resource trees. + *

                      + * Usage: + *

                      + * class Visitor implements IResourceVisitor {
                      + *    public boolean visit(IResource res) {
                      + *       // your code here
                      + *       return true;
                      + *    }
                      + * }
                      + * IResource root = ...;
                      + * root.accept(new Visitor());
                      + * 
                      + *

                      + *

                      + * Clients may implement this interface. + *

                      + * + * @see IResource#accept(IResourceVisitor) + */ +public interface IResourceVisitor { + /** + * Visits the given resource. + * + * @param resource the resource to visit + * @return true if the resource's members should + * be visited; false if they should be skipped + * @exception CoreException if the visit fails for some reason. + */ + public boolean visit(IResource resource) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java new file mode 100644 index 0000000000..0849d7d1fe --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java @@ -0,0 +1,197 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; + +/** + * A context for workspace save operations. + *

                      + * Note that IWorkspace.save uses a + * different save context for each registered participant, + * allowing each to declare whether they have actively + * participated and decide whether to receive a resource + * delta on reactivation. + *

                      + * + * @see IWorkspace#save(boolean, org.eclipse.core.runtime.IProgressMonitor) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ISaveContext { + + /*==================================================================== + * Constants related to save kind + *====================================================================*/ + + /** + * Type constant which identifies a full save. + * + * @see ISaveContext#getKind() + */ + public static final int FULL_SAVE = 1; + + /** + * Type constant which identifies a snapshot. + * + * @see ISaveContext#getKind() + */ + public static final int SNAPSHOT = 2; + + /** + * Type constant which identifies a project save. + * + * @see ISaveContext#getKind() + */ + public static final int PROJECT_SAVE = 3; + + /** + * Returns current files mapped with the ISaveContext.map + * facility or an empty array if there are no mapped files. + * + * @return the files currently mapped by the participant + * + * @see #map(IPath, IPath) + */ + public IPath[] getFiles(); + + /** + * Returns the type of this save. The types can be: + *
                        + *
                      • ISaveContext.FULL_SAVE
                      • + *
                      • ISaveContext.SNAPSHOT
                      • + *
                      • ISaveContext.PROJECT_SAVE
                      • + *
                      + * + * @return the type of the current save + */ + public int getKind(); + + /** + * Returns the number for the previous save in + * which the plug-in actively participated, or 0 + * if the plug-in has never actively participated in a save before. + *

                      + * In the event of an unsuccessful save, this is the value to + * rollback to. + *

                      + * + * @return the previous save number if positive, or 0 + * if never saved before + * @see ISaveParticipant#rollback(ISaveContext) + */ + public int getPreviousSaveNumber(); + + /** + * If the current save is a project save, this method returns the project + * being saved. + * + * @return the project being saved or null if this is not + * project save + * + * @see #getKind() + */ + public IProject getProject(); + + /** + * Returns the number for this save. This number is + * guaranteed to be 1 more than the + * previous save number. + *

                      + * This is the value to use when, for example, creating files + * in which a participant will save its data. + *

                      + * + * @return the save number + * @see ISaveParticipant#saving(ISaveContext) + */ + public int getSaveNumber(); + + /** + * Returns the current location for the given file or + * null if none. + * + * @return the location of a given file or null + * @see #map(IPath, IPath) + * @see ISavedState#lookup(IPath) + */ + public IPath lookup(IPath file); + + /** + * Maps the given plug-in file to its real location. This method is intended to be used + * with ISaveContext.getSaveNumber() to map plug-in configuration + * file names to real locations. + *

                      + * For example, assume a plug-in has a configuration file named "config.properties". + * The map facility can be used to map that logical name onto a real + * name which is specific to a particular save (e.g., 10.config.properties, + * where 10 is the current save number). The paths specified here should + * always be relative to the plug-in state location for the plug-in saving the state. + *

                      + *

                      + * Each save participant must manage the deletion of its old state files. Old state files + * can be discovered using getPreviousSaveNumber or by using + * getFiles to discover the current files and comparing that to the + * list of files on disk. + *

                      + * @param file the logical name of the participant's data file + * @param location the real (i.e., filesystem) name by which the file should be known + * for this save, or null to remove the entry + * @see #lookup(IPath) + * @see #getSaveNumber() + * @see #needSaveNumber() + * @see ISavedState#lookup(IPath) + */ + public void map(IPath file, IPath location); + + /** + * Indicates that the saved workspace tree should be remembered so that a delta + * will be available in a subsequent session when the plug-in re-registers + * to participate in saves. If this method is not called, no resource delta will + * be made available. This facility is not available for marker deltas. + * Plug-ins must assume that all markers may have changed when they are activated. + *

                      + * Note that this is orthogonal to needSaveNumber. That is, + * one can ask for a delta regardless of whether or not one is an active participant. + *

                      + *

                      + * Note that deltas are not guaranteed to be saved even if saving is requested. + * Deltas cannot be supplied where the previous state is too old or has become invalid. + *

                      + *

                      + * This method is only valid for full saves. It is ignored during snapshots + * or project saves. + *

                      + * + * @see IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant) + * @see ISavedState#processResourceChangeEvents(IResourceChangeListener) + */ + public void needDelta(); + + /** + * Indicates that this participant has actively participated in this save. + * If the save is successful, the current save number will be remembered; + * this save number will be the previous save number for subsequent saves + * until the participant again actively participates. + *

                      + * If this method is not called, the plug-in is not deemed to be an active + * participant in this save. + *

                      + *

                      + * Note that this is orthogonal to needDelta. That is, + * one can be an active participant whether or not one asks for a delta. + *

                      + * + * @see IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant) + * @see ISavedState#getSaveNumber() + */ + public void needSaveNumber(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java new file mode 100644 index 0000000000..e3ffa6c893 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveParticipant.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.util.EventListener; +import org.eclipse.core.runtime.CoreException; + +/** + * A participant in the saving of the workspace. + *

                      + * Plug-ins implement this interface and register to participate + * in workspace save operations. + *

                      + *

                      + * Clients may implement this interface. + *

                      + * @see IWorkspace#save(boolean, org.eclipse.core.runtime.IProgressMonitor) + */ +public interface ISaveParticipant extends EventListener { + /** + * Tells this participant that the workspace save operation is now + * complete and it is free to go about its normal business. + * Exceptions are not expected to be thrown at this point, so they + * should be handled internally. + *

                      + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

                      + * + * @param context the save context object + */ + public void doneSaving(ISaveContext context); + + /** + * Tells this participant that the workspace is about to be + * saved. In preparation, the participant is expected to suspend + * its normal operation until further notice. saving + * will be next, followed by either doneSaving + * or rollback depending on whether the workspace + * save was successful. + *

                      + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

                      + * + * @param context the save context object + * @exception CoreException if this method fails to snapshot + * the state of this workspace + */ + public void prepareToSave(ISaveContext context) throws CoreException; + + /** + * Tells this participant to rollback its important state. + * The context's previous state number indicates what it was prior + * to the failed save. + * Exceptions are not expected to be thrown at this point, so they + * should be handled internally. + *

                      + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

                      + * + * @param context the save context object + * @see ISaveContext#getPreviousSaveNumber() + */ + public void rollback(ISaveContext context); + + /** + * Tells this participant to save its important state because + * the workspace is being saved, as described in the supplied + * save context. + *

                      + * Note: This method is called by the platform; it is not intended + * to be called directly by clients. + *

                      + *

                      + * The basic contract for this method is the same for full saves, + * snapshots and project saves: the participant must absolutely guarantee that any + * important user data it has gathered will not be irrecoverably lost + * in the event of a crash. The only difference is in the space-time + * tradeoffs that the participant should make. + *

                        + *
                      • Full saves: the participant is + * encouraged to save additional non-essential information that will aid + * it in retaining user state and configuration information and quickly getting + * back in sync with the state of the platform at a later point. + *
                      • + *
                      • Snapshots: the participant is discouraged from saving non-essential + * information that could be recomputed in the unlikely event of a crash. + * This lifecycle event will happen often and participant actions should take + * an absolute minimum of time. + *
                      • + *
                      • Project saves: the participant should only save project related data. + * It is discouraged from saving non-essential information that could be recomputed + * in the unlikely event of a crash. + *
                      • + *
                      + * For instance, the Java IDE gathers various user preferences and would want to + * make sure that the current settings are safe and sound after a + * save (if not saved immediately). + * The Java IDE would likely save computed image builder state on full saves, + * because this would allow the Java IDE to be restarted later and not + * have to recompile the world at that time. On the other hand, the Java + * IDE would not save the image builder state on a snapshot because + * that information is non-essential; in the unlikely event of a crash, + * the image should be rebuilt either from scratch or from the last saved + * state. + *

                      + *

                      + * The following snippet shows how a plug-in participant would write + * its important state to a file whose name is based on the save + * number for this save operation. + *

                      +	 *     Plugin plugin = ...; // known
                      +	 *     int saveNumber = context.getSaveNumber();
                      +	 *     String saveFileName = "save-" + Integer.toString(saveNumber);
                      +	 *     File f = plugin.getStateLocation().append(saveFileName).toFile();
                      +	 *     plugin.writeImportantState(f);
                      +	 *     context.map(new Path("save"), new Path(saveFileName));
                      +	 *     context.needSaveNumber();
                      +	 *     context.needDelta(); // optional
                      +	 * 
                      + * When the plug-in is reactivated in a subsequent workspace session, + * it needs to re-register to participate in workspace saves. When it + * does so, it is handed back key information about what state it had last + * saved. If it's interested, it can also ask for a resource delta + * describing all resource changes that have happened since then, if this + * information is still available. + * The following snippet shows what a participant plug-in would + * need to do if and when it is reactivated: + *
                      +	 *     IWorkspace ws = ...; // known
                      +	 *     Plugin plugin = ...; // known
                      +	 *     ISaveParticipant saver = ...; // known
                      +	 *     ISavedState ss = ws.addSaveParticipant(plugin, saver);
                      +	 *     if (ss == null) {
                      +	 *         // activate for very first time
                      +	 *         plugin.buildState();
                      +	 *     } else {
                      +	 *         String saveFileName = ss.lookup(new Path("save"));
                      +	 *         File f = plugin.getStateLocation().append(saveFileName).toFile();
                      +	 *         plugin.readImportantState(f);
                      +	 *         IResourceChangeListener listener = new IResourceChangeListener() {
                      +	 *             public void resourceChanged(IResourceChangeEvent event) {
                      +	 *                 IResourceDelta delta = event.getDelta();
                      +	 *                 if (delta != null) {
                      +	 *                     // fast reactivation using delta
                      +	 *                     plugin.updateState(delta);
                      +	 *                 } else {
                      +	 *                     // slower reactivation without benefit of delta
                      +	 *                     plugin.rebuildState();
                      +	 *                 }
                      +	 *         };
                      +	 *         ss.processResourceChangeEvents(listener);
                      +	 *     }
                      +	 * 
                      + *

                      + * + * @param context the save context object + * @exception CoreException if this method fails + * @see ISaveContext#getSaveNumber() + */ + public void saving(ISaveContext context) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java new file mode 100644 index 0000000000..2830d1526b --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.IPath; + +/** + * A data structure returned by {@link IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant)} + * containing a save number and an optional resource delta. + * + * @see IWorkspace#addSaveParticipant(org.eclipse.core.runtime.Plugin, ISaveParticipant) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ISavedState { + /** + * Returns the files mapped with the {@link ISaveContext#map(IPath, IPath)} + * facility. Returns an empty array if there are no mapped files. + * + * @return the files currently mapped by the participant + * + * @see #lookup(IPath) + * @see ISaveContext#map(IPath, IPath) + */ + public IPath[] getFiles(); + + /** + * Returns the save number for the save participant. + * This is the save number of the last successful save in which the plug-in + * actively participated, or 0 if the plug-in has + * never actively participated in a successful save. + * + * @return the save number + */ + public int getSaveNumber(); + + /** + * Returns the mapped location associated with the given path + * or null if none. + * + * @return the mapped location of a given path + * @see #getFiles() + * @see ISaveContext#map(IPath, IPath) + */ + public IPath lookup(IPath file); + + /** + * Used to receive notification of changes that might have happened + * while this plug-in was not active. The listener receives notifications of changes to + * the workspace resource tree since the time this state was saved. After this + * method is run, the delta is forgotten. Subsequent calls to this method + * will have no effect. + *

                      + * No notification is received in the following cases: + *

                        + *
                      • if a saved state was never recorded ({@link ISaveContext#needDelta()} + * was not called)
                      • + *
                      • a saved state has since been forgotten (using {@link IWorkspace#forgetSavedTree(String)})
                      • + *
                      • a saved state has been deemed too old or has become invalid
                      • + *
                      + *

                      + * All clients should have a contingency plan in place in case + * a changes are not available (the case should be very similar + * to the first time a plug-in is activated, and only has the + * current state of the workspace to work from). + *

                      + *

                      + * The supplied event is of type {@link IResourceChangeEvent#POST_BUILD} + * and contains the delta detailing changes since this plug-in last participated + * in a save. This event object (and the resource delta within it) is valid only + * for the duration of the invocation of this method. + *

                      + * + * @param listener the listener + * @see ISaveContext#needDelta() + * @see IResourceChangeListener + */ + public void processResourceChangeEvents(IResourceChangeListener listener); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java new file mode 100644 index 0000000000..58034e65bb --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import java.io.InputStream; +import org.eclipse.core.runtime.*; + +/** + * A storage object represents a set of bytes which can be accessed. + * These may be in the form of an IFile or IFileState + * or any other object supplied by user code. The main role of an IStorage + * is to provide a uniform API for access to, and presentation of, its content. + *

                      + * Storage objects implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

                      + * Clients may implement this interface. + *

                      + *

                      + */ +public interface IStorage extends IAdaptable { + /** + * Returns an open input stream on the contents of this storage. + * The caller is responsible for closing the stream when finished. + * + * @return an input stream containing the contents of this storage + * @exception CoreException if the contents of this storage could + * not be accessed. See any refinements for more information. + */ + public InputStream getContents() throws CoreException; + + /** + * Returns the full path of this storage. The returned value + * depends on the implementor/extender. A storage need not + * have a path. + * + * @return the path related to the data represented by this storage or + * null if none. + */ + public IPath getFullPath(); + + /** + * Returns the name of this storage. + * The name of a storage is synonymous with the last segment + * of its full path though if the storage does not have a path, + * it may still have a name. + * + * @return the name of the data represented by this storage, + * or null if this storage has no name + * @see #getFullPath() + */ + public String getName(); + + /** + * Returns whether this storage is read-only. + * + * @return true if this storage is read-only + */ + public boolean isReadOnly(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java new file mode 100644 index 0000000000..43582635e3 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.QualifiedName; + +/** + * A synchronizer which maintains a list of registered partners and, on behalf of + * each partner, it keeps resource level synchronization information + * (a byte array). Sync info is saved only when the workspace is saved. + * + * @see IWorkspace#getSynchronizer() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ISynchronizer { + /** + * Visits the given resource and its descendents with the specified visitor + * if sync information for the given sync partner is found on the resource. If + * sync information for the given sync partner is not found on the resource, + * still visit the children of the resource if the depth specifies to do so. + * + * @param partner the sync partner name + * @param start the parent resource to start the visitation + * @param visitor the visitor to use when visiting the resources + * @param depth the depth to which members of this resource should be + * visited. One of IResource.DEPTH_ZERO, IResource.DEPTH_ONE, + * or IResource.DEPTH_INFINITE. + * @exception CoreException if this operation fails. Reasons include: + *
                        + *
                      • The resource does not exist.
                      • + *
                      • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
                      • + *
                      + */ + public void accept(QualifiedName partner, IResource start, IResourceVisitor visitor, int depth) throws CoreException; + + /** + * Adds the named synchronization partner to this synchronizer's + * registry of partners. Once a partner's name has been registered, sync + * information can be set and retrieve on resources relative to the name. + * Adding a sync partner multiple times has no effect. + * + * @param partner the partner name to register + * @see #remove(QualifiedName) + */ + public void add(QualifiedName partner); + + /** + * Discards the named partner's synchronization information + * associated with the specified resource and its descendents to the + * specified depth. + * + * @param partner the sync partner name + * @param resource the resource + * @param depth the depth to which members of this resource should be + * visited. One of IResource.DEPTH_ZERO, IResource.DEPTH_ONE, + * or IResource.DEPTH_INFINITE. + * @exception CoreException if this operation fails. Reasons include: + *
                        + *
                      • The resource does not exist.
                      • + *
                      • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
                      • + *
                      + */ + public void flushSyncInfo(QualifiedName partner, IResource resource, int depth) throws CoreException; + + /** + * Returns a list of synchronization partner names currently registered + * with this synchronizer. Returns an empty array if there are no + * registered sync partners. + * + * @return a list of sync partner names + */ + public QualifiedName[] getPartners(); + + /** + * Returns the named sync partner's synchronization information for the given resource. + * Returns null if no information is found. + * + * @param partner the sync partner name + * @param resource the resource + * @return the synchronization information, or null if none + * @exception CoreException if this operation fails. Reasons include: + *
                        + *
                      • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
                      • + *
                      + */ + public byte[] getSyncInfo(QualifiedName partner, IResource resource) throws CoreException; + + /** + * Removes the named synchronization partner from this synchronizer's + * registry. Does nothing if the partner is not registered. + * This discards all sync information for the defunct partner. + * After a partner has been unregistered, sync information for it can no + * longer be stored on resources. + * + * @param partner the partner name to remove from the registry + * @see #add(QualifiedName) + */ + public void remove(QualifiedName partner); + + /** + * Sets the named sync partner's synchronization information for the given resource. + * If the given info is non-null and the resource neither exists + * nor is a phantom, this method creates a phantom resource to hang on to the info. + * If the given info is null, any sync info for the resource stored by the + * given sync partner is discarded; in some cases, this may result in the deletion + * of a phantom resource if there is no more sync info to maintain for that resource. + *

                      + * Sync information is not stored on the workspace root. Attempts to set information + * on the root will be ignored. + *

                      + * + * @param partner the sync partner name + * @param resource the resource + * @param info the synchronization information, or null + * @exception CoreException if this operation fails. Reasons include: + *
                        + *
                      • IResourceStatus.PARTNER_NOT_REGISTERED + The sync partner is not registered.
                      • + *
                      + */ + public void setSyncInfo(QualifiedName partner, IResource resource, byte[] info) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java new file mode 100644 index 0000000000..b5079cc460 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspace.java @@ -0,0 +1,1764 @@ +/******************************************************************************* + * 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 + * Red Hat Incorporated - loadProjectDescription(InputStream) + * Broadcom Corporation - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.io.InputStream; +import java.net.URI; +import java.util.Map; +import org.eclipse.core.resources.team.FileModificationValidationContext; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * Workspaces are the basis for Eclipse Platform resource management. There is + * only one workspace per running platform. All resources exist in the context + * of this workspace. + *

                      + * A workspace corresponds closely to discreet areas in the local file system. + * Each project in a workspace maps onto a specific area of the file system. The + * folders and files within a project map directly onto the corresponding + * directories and files in the file system. One sub-directory, the workspace + * metadata area, contains internal information about the workspace and its + * resources. This metadata area should be accessed only by the Platform or via + * Platform API calls. + *

                      + *

                      + * Workspaces add value over using the file system directly in that they allow + * for comprehensive change tracking (through IResourceDelta s), + * various forms of resource metadata (e.g., markers and properties) as well as + * support for managing application/tool state (e.g., saving and restoring). + *

                      + *

                      + * The workspace as a whole is thread safe and allows one writer concurrent with + * multiple readers. It also supports mechanisms for saving and snapshotting the + * current resource state. + *

                      + *

                      + * The workspace is provided by the Resources plug-in and is automatically + * created when that plug-in is activated. The default workspace data area + * (i.e., where its resources are stored) overlap exactly with the platform's + * data area. That is, by default, the workspace's projects are found directly + * in the platform's data area. Individual project locations can be specified + * explicitly. + *

                      + *

                      + * The workspace resource namespace is always case-sensitive and + * case-preserving. Thus the workspace allows multiple sibling resources to exist + * with names that differ only in case. The workspace also imposes no + * restrictions on valid characters in resource names, the length of resource names, + * or the size of resources on disk. In situations where one or more resources + * are stored in a file system that is not case-sensitive, or that imposes restrictions + * on resource names, any failure to store or retrieve those resources will + * be propagated back to the caller of workspace API. + *

                      + *

                      + * Workspaces implement the IAdaptable interface; extensions are + * managed by the platform's adapter manager. + *

                      + * + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IWorkspace extends IAdaptable { + /** + * flag constant (bit mask value 1) indicating that resource change + * notifications should be avoided during the invocation of a compound + * resource changing operation. + * + * @see IWorkspace#run(ICoreRunnable, ISchedulingRule, int, IProgressMonitor) + * @since 3.0 + */ + public static final int AVOID_UPDATE = 1; + + /** + * Constant that can be passed to {@link #validateEdit(org.eclipse.core.resources.IFile[], Object)} + * to indicate that the caller does not have access to a UI context but would still + * like to have UI-based validation if possible. + * @since 3.3 + * @see #validateEdit(IFile[], Object) + */ + public static final Object VALIDATE_PROMPT = FileModificationValidationContext.VALIDATE_PROMPT; + + /** + * The name of the IWorkspace OSGi service (value "org.eclipse.core.resources.IWorkspace"). + * @since 3.5 + */ + public static final String SERVICE_NAME = IWorkspace.class.getName(); + + /** + * Adds the given listener for resource change events to this workspace. Has + * no effect if an identical listener is already registered. + *

                      + * This method is equivalent to: + * + *

                      +	 * addResourceChangeListener(listener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE);
                      +	 * 
                      + * + *

                      + * + * @param listener the listener + * @see IResourceChangeListener + * @see IResourceChangeEvent + * @see #addResourceChangeListener(IResourceChangeListener, int) + * @see #removeResourceChangeListener(IResourceChangeListener) + */ + public void addResourceChangeListener(IResourceChangeListener listener); + + /** + * Adds the given listener for the specified resource change events to this + * workspace. Has no effect if an identical listener is already registered + * for these events. After completion of this method, the given listener + * will be registered for exactly the specified events. If they were + * previously registered for other events, they will be de-registered. + *

                      + * Once registered, a listener starts receiving notification of changes to + * resources in the workspace. The resource deltas in the resource change + * event are rooted at the workspace root. Most resource change + * notifications occur well after the fact; the exception is + * pre-notification of impending project closures and deletions. The + * listener continues to receive notifications until it is replaced or + * removed. + *

                      + *

                      + * Listeners can listen for several types of event as defined in + * IResourceChangeEvent. Clients are free to register for + * any number of event types however if they register for more than one, it + * is their responsibility to ensure they correctly handle the case where + * the same resource change shows up in multiple notifications. Clients are + * guaranteed to receive only the events for which they are registered. + *

                      + * + * @param listener the listener + * @param eventMask the bit-wise OR of all event types of interest to the + * listener + * @see IResourceChangeListener + * @see IResourceChangeEvent + * @see #removeResourceChangeListener(IResourceChangeListener) + */ + public void addResourceChangeListener(IResourceChangeListener listener, int eventMask); + + /** + * Registers the given plug-in's workspace save participant, and returns an + * object describing the workspace state at the time of the last save in + * which the plug-in participated. + *

                      + * Once registered, the workspace save participant will actively participate + * in the saving of this workspace. + *

                      + * + * @param plugin the plug-in + * @param participant the participant + * @return the last saved state in which the plug-in participated, or + * null if the plug-in has not participated before + * @exception CoreException if the method fails to add the participant. + * Reasons include: + *
                        + *
                      • The previous state could not be recovered.
                      • + *
                      + * @see ISaveParticipant + * @see #removeSaveParticipant(Plugin) + * @deprecated Use {@link #addSaveParticipant(String, ISaveParticipant)} instead + */ + @Deprecated + public ISavedState addSaveParticipant(Plugin plugin, ISaveParticipant participant) throws CoreException; + + /** + * Registers the given plug-in's workspace save participant, and returns an + * object describing the workspace state at the time of the last save in + * which the bundle participated. + *

                      + * Once registered, the workspace save participant will actively participate + * in the saving of this workspace. + *

                      + * + * @param pluginId the unique identifier of the plug-in + * @param participant the participant + * @return the last saved state in which the plug-in participated, or + * null if the plug-in has not participated before + * @exception CoreException if the method fails to add the participant. + * Reasons include: + *
                        + *
                      • The previous state could not be recovered.
                      • + *
                      + * @see ISaveParticipant + * @see #removeSaveParticipant(String) + * @since 3.6 + */ + public ISavedState addSaveParticipant(String pluginId, ISaveParticipant participant) throws CoreException; + + /** + * Builds all projects in this workspace. Projects are built in the order + * specified in this workspace's description. Projects not mentioned in the + * order or for which the order cannot be determined are built in an + * undefined order after all other projects have been built. If no order is + * specified, the workspace computes an order determined by project + * references. + *

                      + * This method may change resources; these changes will be reported in a + * subsequent resource change event. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

                      + * + * @param kind the kind of build being requested. Valid values are + *
                        + *
                      • {@link IncrementalProjectBuilder#FULL_BUILD}- indicates a full build.
                      • + *
                      • {@link IncrementalProjectBuilder#INCREMENTAL_BUILD}- indicates a incremental build.
                      • + *
                      • {@link IncrementalProjectBuilder#CLEAN_BUILD}- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states.
                      • + *
                      + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic {@link IResourceStatus#BUILD_FAILED} + * code, but it could also be any other status code; it might + * also be a {@link MultiStatus}. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see IWorkspace#build(IBuildConfiguration[], int, boolean, IProgressMonitor) + * @see IProject#build(int, IProgressMonitor) + * @see #computeProjectOrder(IProject[]) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @see IResourceRuleFactory#buildRule() + */ + public void build(int kind, IProgressMonitor monitor) throws CoreException; + + /** + * Build the build configurations specified in the passed in build configuration array. + *

                      + * Build order is determined by the workspace description and the project build configuration + * reference graph. + *

                      + *

                      + * If buildReferences is true, build configurations reachable through the build configuration graph are + * built as part of this build invocation. + *

                      + *

                      + * This method may change resources; these changes will be reported in a + * subsequent resource change event. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

                      + * @param buildConfigs array of configurations to build + * @param kind the kind of build being requested. Valid values are + *
                        + *
                      • {@link IncrementalProjectBuilder#FULL_BUILD}- indicates a full build.
                      • + *
                      • {@link IncrementalProjectBuilder#INCREMENTAL_BUILD}- indicates a incremental build.
                      • + *
                      • {@link IncrementalProjectBuilder#CLEAN_BUILD}- indicates a clean request. Clean does + * not actually build anything, but rather discards all problems and build states.
                      • + *
                      + * @param buildReferences boolean indicating if references should be transitively built. + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the build fails. + * The status contained in the exception may be a generic {@link IResourceStatus#BUILD_FAILED} + * code, but it could also be any other status code; it might + * also be a {@link MultiStatus}. + * @exception OperationCanceledException if the operation is canceled. + * Cancellation can occur even if no progress monitor is provided. + * + * @see IProject#build(int, IProgressMonitor) + * @see IncrementalProjectBuilder#FULL_BUILD + * @see IncrementalProjectBuilder#INCREMENTAL_BUILD + * @see IncrementalProjectBuilder#CLEAN_BUILD + * @see IResourceRuleFactory#buildRule() + * @since 3.7 + */ + public void build(IBuildConfiguration[] buildConfigs, int kind, boolean buildReferences, IProgressMonitor monitor) throws CoreException; + + /** + * Checkpoints the operation currently in progress. This method is used in + * the middle of a group of operations to force a background autobuild (if + * the build argument is true) and send an interim notification of resource + * change events. + *

                      + * When invoked in the dynamic scope of a call to the + * IWorkspace.run method, this method reports a single + * resource change event describing the net effect of all changes done to + * resources since the last round of notifications. When the outermost + * run method eventually completes, it will do another + * autobuild (if enabled) and report the resource changes made after this + * call. + *

                      + *

                      + * This method has no effect if invoked outside the dynamic scope of a call + * to the IWorkspace.run method. + *

                      + *

                      + * This method should be used under controlled circumstance (e.g., to break + * up extremely long-running operations). + *

                      + * + * @param build whether or not to run a build + * @see IWorkspace#run(ICoreRunnable, ISchedulingRule, int, IProgressMonitor) + */ + public void checkpoint(boolean build); + + /** + * Returns the prerequisite ordering of the given projects. The computation + * is done by interpreting the projects' active build configuration references + * as dependency relationships. + * For example if A references B and C, and C references B, this method, + * given the list A, B, C will return the order B, C, A. That is, projects + * with no dependencies are listed first. + *

                      + * The return value is a two element array of project arrays. The first + * project array is the list of projects which could be sorted (as outlined + * above). The second element of the return value is an array of the + * projects which are ambiguously ordered (e.g., they are part of a cycle). + *

                      + *

                      + * Cycles and ambiguities are handled by elimination. Projects involved in + * cycles are simply cut out of the ordered list and returned in an + * undefined order. Closed and non-existent projects are ignored and do not + * appear in the returned value at all. + *

                      + * + * @param projects the projects to order + * @return the projects in sorted order and a list of projects which could + * not be ordered + * @deprecated Replaced by IWorkspace.computeProjectOrder, + * which produces a more usable result when there are cycles in project + * reference graph. + */ + @Deprecated + public IProject[][] computePrerequisiteOrder(IProject[] projects); + + /** + * Data structure for holding the multi-part outcome of + * IWorkspace.computeProjectOrder. + *

                      + * This class is not intended to be instantiated by clients. + *

                      + * + * @see IWorkspace#computeProjectOrder(IProject[]) + * @since 2.1 + */ + public final class ProjectOrder { + /** + * Creates an instance with the given values. + *

                      + * This class is not intended to be instantiated by clients. + *

                      + * + * @param projects initial value of projects field + * @param hasCycles initial value of hasCycles field + * @param knots initial value of knots field + */ + public ProjectOrder(IProject[] projects, boolean hasCycles, IProject[][] knots) { + this.projects = projects; + this.hasCycles = hasCycles; + this.knots = knots; + } + + /** + * A list of projects ordered so as to honor the project reference, and + * build configuration reference, relationships between these projects + * wherever possible. + * The elements are a subset of the ones passed as the projects + * parameter to IWorkspace.computeProjectOrder, where + * inaccessible (closed or non-existent) projects have been omitted. + */ + public IProject[] projects; + /** + * Indicates whether any of the accessible projects in + * projects are involved in non-trivial cycles. + * true if the reference graph contains at least + * one cycle involving two or more of the projects in + * projects, and false if none of the + * projects in projects are involved in cycles. + */ + public boolean hasCycles; + /** + * A list of knots in the project reference graph. This list is empty if + * the project reference graph does not contain cycles. If the project + * reference graph contains cycles, each element is a knot of two or + * more accessible projects from projects that are + * involved in a cycle of mutually dependent references. + */ + public IProject[][] knots; + } + + /** + * Computes a total ordering of the given projects based on both static and + * dynamic project references. If an existing and open project P references + * another existing and open project Q also included in the list, then Q + * should come before P in the resulting ordering. Closed and non-existent + * projects are ignored, and will not appear in the result. References to + * non-existent or closed projects are also ignored, as are any + * self-references. The total ordering is always consistent with the global + * total ordering of all open projects in the workspace. + *

                      + * When there are choices, the choice is made in a reasonably stable way. + * For example, given an arbitrary choice between two projects, the one with + * the lower collating project name is usually selected. + *

                      + *

                      + * When the project reference graph contains cyclic references, it is + * impossible to honor all of the relationships. In this case, the result + * ignores as few relationships as possible. For example, if P2 references + * P1, P4 references P3, and P2 and P3 reference each other, then exactly + * one of the relationships between P2 and P3 will have to be ignored. The + * outcome will be either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result + * also contains complete details of any cycles present. + *

                      + *

                      + * This method is time-consuming and should not be called unnecessarily. + * There are a very limited set of changes to a workspace that could affect + * the outcome: creating, renaming, or deleting a project; opening or + * closing a project; adding or removing a project reference. + *

                      + * + * @param projects the projects to order + * @return result describing the project order + * @since 2.1 + */ + public ProjectOrder computeProjectOrder(IProject[] projects); + + /** + * Copies the given sibling resources so that they are located as members of + * the resource at the given path; the names of the copies are the same as + * the corresponding originals. + *

                      + * This is a convenience method, fully equivalent to: + * + *

                      +	 * copy(resources, destination, (force ? IResource.FORCE : IResource.NONE), monitor);
                      +	 * 
                      + * + *

                      + *

                      + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been added to the new parent. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

                      + * + * @param resources the resources to copy + * @param destination the destination container path + * @param force a flag controlling whether resources that are not in sync + * with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status object with code OK if there were no + * problems; otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to copy some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #copy(IResource[],IPath,int,IProgressMonitor) + */ + public IStatus copy(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Copies the given sibling resources so that they are located as members of + * the resource at the given path; the names of the copies are the same as + * the corresponding originals. + *

                      + * This method can be expressed as a series of calls to + * IResource.copy(IPath,int,IProgressMonitor), with "best + * effort" semantics: + *

                        + *
                      • Resources are copied in the order specified, using the given update + * flags.
                      • + *
                      • Duplicate resources are only copied once.
                      • + *
                      • The method fails if the resources are not all siblings.
                      • + *
                      • The failure of an individual copy does not necessarily prevent the + * method from attempting to copy other resources.
                      • + *
                      • The method fails if there are projects among the resources.
                      • + *
                      • The method fails if the path of the resources is a prefix of the + * destination path.
                      • + *
                      • This method also fails if one or more of the individual resource + * copy steps fails.
                      • + *
                      + *

                      + *

                      + * After successful completion, corresponding new resources will now exist + * as members of the resource at the given path. + *

                      + *

                      + * The supplied path may be absolute or relative. Absolute paths fully + * specify the new location for the resource, including its project. + * Relative paths are considered to be relative to the container of the + * resources being copied. A trailing separator is ignored. + *

                      + *

                      + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been added to the new parent. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

                      + * + * @param resources the resources to copy + * @param destination the destination container path + * @param updateFlags bit-wise or of update flag constants + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status object with code OK if there were no + * problems; otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to copy some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. Reasons include: + *
                        + *
                      • One of the resources does not exist.
                      • + *
                      • The resources are not siblings.
                      • + *
                      • One of the resources, or one of its descendents, is not local.
                      • + *
                      • The resource corresponding to the destination path does not exist. + *
                      • + *
                      • The resource corresponding to the parent destination path is a + * closed project.
                      • + *
                      • A corresponding target resource does exist.
                      • + *
                      • A resource of a different type exists at the target path.
                      • + *
                      • One of the resources is a project.
                      • + *
                      • The path of one of the resources is a prefix of the destination + * path.
                      • + *
                      • One of the resources, or one of its descendents, is out of sync with + * the local file system and FORCE is not specified.
                      • + *
                      • Resource changes are disallowed during certain types of resource + * change event notification. See IResourceChangeEvent for + * more details.
                      • + *
                      + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#copy(IPath,int,IProgressMonitor) + * @see IResourceRuleFactory#copyRule(IResource, IResource) + * @since 2.0 + */ + public IStatus copy(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes the given resources. + *

                      + * This is a convenience method, fully equivalent to: + * + *

                      +	 * delete(resources, IResource.KEEP_HISTORY | (force ? IResource.FORCE : IResource.NONE), monitor);
                      +	 * 
                      + * + *

                      + *

                      + * This method changes resources; these changes will be reported in a + * subsequent resource change event. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

                      + * + * @param resources the resources to delete + * @param force a flag controlling whether resources that are not in sync + * with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to delete some resource. The + * status contained in the exception is a multi-status indicating where the + * individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #delete(IResource[],int,IProgressMonitor) + */ + public IStatus delete(IResource[] resources, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Deletes the given resources. + *

                      + * This method can be expressed as a series of calls to + * IResource.delete(int,IProgressMonitor). + *

                      + *

                      + * The semantics of multiple deletion are: + *

                        + *
                      • Resources are deleted in the order presented, using the given update + * flags.
                      • + *
                      • Resources that do not exist are ignored.
                      • + *
                      • An individual deletion fails if the resource still exists + * afterwards.
                      • + *
                      • The failure of an individual deletion does not prevent the method + * from attempting to delete other resources.
                      • + *
                      • This method fails if one or more of the individual resource + * deletions fails; that is, if at least one of the resources in the list + * still exists at the end of this method.
                      • + *
                      + *

                      + *

                      + * This method changes resources; these changes will be reported in a + * subsequent resource change event. + *

                      + *

                      + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

                      + * + * @param resources the resources to delete + * @param updateFlags bit-wise or of update flag constants + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages + * @exception CoreException if the method fails to delete some resource. The + * status contained in the exception is a multi-status indicating where the + * individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + * @see IResourceRuleFactory#deleteRule(IResource) + * @since 2.0 + */ + public IStatus delete(IResource[] resources, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Removes the given markers from the resources with which they are + * associated. Markers that do not exist are ignored. + *

                      + * This method changes resources; these changes will be reported in a + * subsequent resource change event. + *

                      + * + * @param markers the markers to remove + * @exception CoreException if this method fails. Reasons include: + *
                        + *
                      • Resource changes are disallowed during certain types of resource + * change event notification. See IResourceChangeEvent for + * more details.
                      • + *
                      + * @see IResourceRuleFactory#markerRule(IResource) + */ + public void deleteMarkers(IMarker[] markers) throws CoreException; + + /** + * Forgets any resource tree being saved for the plug-in with the given + * name. If the plug-in id is null, all trees are forgotten. + *

                      + * Clients should not call this method unless they have a reason to do so. A + * plug-in which uses ISaveContext.needDelta in the process + * of a save indicates that it would like to be fed the resource delta the + * next time it is reactivated. If a plug-in never gets reactivated (or if + * it fails to successfully register to participate in workspace saves), the + * workspace nevertheless retains the necessary information to generate the + * resource delta if asked. This method allows such a long term leak to be + * plugged. + *

                      + * + * @param pluginId the unique identifier of the plug-in, or null + * @see ISaveContext#needDelta() + */ + public void forgetSavedTree(String pluginId); + + /** + * Returns all filter matcher descriptors known to this workspace. Returns an empty + * array if there are no installed filter matchers. + * + * @return the filter matcher descriptors known to this workspace + * @since 3.6 + */ + public IFilterMatcherDescriptor[] getFilterMatcherDescriptors(); + + /** + * Returns the filter descriptor with the given unique identifier, or + * null if there is no such filter. + * + * @param filterMatcherId the filter matcher extension identifier (e.g. + * "com.example.coolFilter"). + * @return the filter matcher descriptor, or null + * @since 3.6 + */ + public IFilterMatcherDescriptor getFilterMatcherDescriptor(String filterMatcherId); + + /** + * Returns all nature descriptors known to this workspace. Returns an empty + * array if there are no installed natures. + * + * @return the nature descriptors known to this workspace + * @since 2.0 + */ + public IProjectNatureDescriptor[] getNatureDescriptors(); + + /** + * Returns the nature descriptor with the given unique identifier, or + * null if there is no such nature. + * + * @param natureId the nature extension identifier (e.g. + * "com.example.coolNature"). + * @return the nature descriptor, or null + * @since 2.0 + */ + public IProjectNatureDescriptor getNatureDescriptor(String natureId); + + /** + * Finds all dangling project references in this workspace. Projects which + * are not open are ignored. Returns a map with one entry for each open + * project in the workspace that has at least one dangling project + * reference; the value of the entry is an array of projects which are + * referenced by that project but do not exist in the workspace. Returns an + * empty Map if there are no projects in the workspace. + * + * @return a map (key type: IProject, value type: + * IProject[]) from project to dangling project references + */ + public Map getDanglingReferences(); + + /** + * Returns the workspace description. This object is responsible for + * defining workspace preferences. The returned value is a modifiable copy + * but changes are not automatically applied to the workspace. In order to + * changes take effect, IWorkspace.setDescription needs to be + * called. The workspace description values are store in the preference + * store. + * + * @return the workspace description + * @see #setDescription(IWorkspaceDescription) + */ + public IWorkspaceDescription getDescription(); + + /** + * Returns the root resource of this workspace. + * + * @return the workspace root + */ + public IWorkspaceRoot getRoot(); + + /** + * Returns a factory for obtaining scheduling rules prior to modifying + * resources in the workspace. + * + * @see IResourceRuleFactory + * @return a resource rule factory + * @since 3.0 + */ + public IResourceRuleFactory getRuleFactory(); + + /** + * Returns the synchronizer for this workspace. + * + * @return the synchronizer + * @see ISynchronizer + */ + public ISynchronizer getSynchronizer(); + + /** + * Returns whether this workspace performs autobuilds. + * + * @return true if autobuilding is on, false + * otherwise + */ + public boolean isAutoBuilding(); + + /** + * Returns whether the workspace tree is currently locked. Resource changes + * are disallowed during certain types of resource change event + * notification. See IResourceChangeEvent for more details. + * + * @return boolean true if the workspace tree is locked, + * false otherwise + * @see IResourceChangeEvent + * @since 2.1 + */ + public boolean isTreeLocked(); + + /** + * Reads the project description file (".project") from the given location + * in the local file system. This object is useful for discovering the + * correct name for a project before importing it into the workspace. + *

                      + * The returned value is writeable. + *

                      + * + * @param projectDescriptionFile the path in the local file system of an + * existing project description file + * @return a new project description + * @exception CoreException if the operation failed. Reasons include: + *
                        + *
                      • The project description file does not exist.
                      • + *
                      • The file cannot be opened or read.
                      • + *
                      • The file cannot be parsed as a legal project description.
                      • + * + * @see #newProjectDescription(String) + * @see IProject#getDescription() + * @since 2.0 + */ + public IProjectDescription loadProjectDescription(IPath projectDescriptionFile) throws CoreException; + + /** + * Reads the project description file (".project") from the given InputStream. + * This object will not attempt to set the location since the project may not + * have a valid location on the local file system. + * This object is useful for discovering the correct name for a project before + * importing it into the workspace. + *

                        + * The returned value is writeable. + *

                        + * + * @param projectDescriptionFile an InputStream pointing to an existing project + * description file + * @return a new project description + * @exception CoreException if the operation failed. Reasons include: + *
                          + *
                        • The stream could not be read.
                        • + *
                        • The stream does not contain a legal project description.
                        • + * + * @see #newProjectDescription(String) + * @see IProject#getDescription() + * @see IWorkspace#loadProjectDescription(IPath) + * @since 3.1 + */ + public IProjectDescription loadProjectDescription(InputStream projectDescriptionFile) throws CoreException; + + /** + * Moves the given sibling resources so that they are located as members of + * the resource at the given path; the names of the new members are the + * same. + *

                          + * This is a convenience method, fully equivalent to: + * + *

                          +	 * move(resources, destination, IResource.KEEP_HISTORY | (force ? IResource.FORCE : IResource.NONE), monitor);
                          +	 * 
                          + * + *

                          + *

                          + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been removed from their parent and that corresponding + * resources have been added to the new parent. Additional information + * provided with resource delta shows that these additions and removals are + * pairwise related. + *

                          + *

                          + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

                          + * + * @param resources the resources to move + * @param destination the destination container path + * @param force a flag controlling whether resources that are not in sync + * with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages. + * @exception CoreException if the method fails to move some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #move(IResource[],IPath,int,IProgressMonitor) + */ + public IStatus move(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Moves the given sibling resources so that they are located as members of + * the resource at the given path; the names of the new members are the + * same. + *

                          + * This method can be expressed as a series of calls to + * IResource.move, with "best effort" semantics: + *

                            + *
                          • Resources are moved in the order specified.
                          • + *
                          • Duplicate resources are only moved once.
                          • + *
                          • The force flag has the same meaning as it does on the + * corresponding single-resource method.
                          • + *
                          • The method fails if the resources are not all siblings.
                          • + *
                          • The method fails the path of any of the resources is a prefix of the + * destination path.
                          • + *
                          • The failure of an individual move does not necessarily prevent the + * method from attempting to move other resources.
                          • + *
                          • This method also fails if one or more of the individual resource + * moves fails; that is, if at least one of the resources in the list still + * exists at the end of this method.
                          • + *
                          • History is kept for moved files. When projects are moved, no history + * is kept
                          • + *
                          + *

                          + *

                          + * After successful completion, the resources and descendents will no longer + * exist; but corresponding new resources will now exist as members of the + * resource at the given path. + *

                          + *

                          + * The supplied path may be absolute or relative. Absolute paths fully + * specify the new location for the resource, including its project. + * Relative paths are considered to be relative to the container of the + * resources being moved. A trailing separator is ignored. + *

                          + *

                          + * This method changes resources; these changes will be reported in a + * subsequent resource change event that will include an indication that the + * resources have been removed from their parent and that corresponding + * resources have been added to the new parent. Additional information + * provided with resource delta shows that these additions and removals are + * pairwise related. + *

                          + *

                          + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. + *

                          + * + * @param resources the resources to move + * @param destination the destination container path + * @param updateFlags bit-wise or of update flag constants + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return status with code OK if there were no problems; + * otherwise a description (possibly a multi-status) consisting of + * low-severity warnings or informational messages. + * @exception CoreException if the method fails to move some resources. The + * status contained in the exception may be a multi-status indicating where + * the individual failures occurred. Reasons include: + *
                            + *
                          • One of the resources does not exist.
                          • + *
                          • The resources are not siblings.
                          • + *
                          • One of the resources, or one of its descendents, is not local.
                          • + *
                          • The resource corresponding to the destination path does not exist. + *
                          • + *
                          • The resource corresponding to the parent destination path is a + * closed project.
                          • + *
                          • A corresponding target resource does exist.
                          • + *
                          • A resource of a different type exists at the target path.
                          • + *
                          • The path of one of the resources is a prefix of the destination + * path.
                          • + *
                          • One of the resources, or one of its descendents, is out of sync with + * the local file system and FORCE is false. + *
                          • + *
                          • Resource changes are disallowed during certain types of resource + * change event notification. See IResourceChangeEvent for + * more details.
                          • + *
                          + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(IPath,int,IProgressMonitor) + * @see IResourceRuleFactory#moveRule(IResource, IResource) + * @since 2.0 + */ + public IStatus move(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException; + + /** + * Returns a new build configuration for the project, with the given name. + * The name is a human readable unique name for the build configuration in the + * project. The project need not exist. + *

                          + * This API can be used to create {@link IBuildConfiguration}s that will be used as references + * to {@link IBuildConfiguration}s in other projects. These references are set using + * {@link IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[])} + * and may have a null configuration name which will resolve to the referenced + * project's active configuration when the reference is used. + *

                          + *

                          + * Build configuration do not become part of a project + * description until set using {@link IProjectDescription#setBuildConfigs(String[])}. + *

                          + * + * @param projectName the name of the project on which the configuration will exist + * @param configName the name of the new build configuration + * @return a build configuration + * @see IProjectDescription#setBuildConfigs(String[]) + * @see IProjectDescription#setBuildConfigReferences(String, IBuildConfiguration[]) + * @see IBuildConfiguration + * @since 3.7 + */ + public IBuildConfiguration newBuildConfig(String projectName, String configName); + + /** + * Creates and returns a new project description for a project with the + * given name. This object is useful when creating, moving or copying + * projects. + *

                          + * The project description is initialized to: + *

                            + *
                          • the given project name
                          • + *
                          • no references to other projects
                          • + *
                          • an empty build spec
                          • + *
                          • an empty comment
                          • + *
                          + *

                          + *

                          + * The returned value is writeable. + *

                          + * + * @param projectName the name of the project + * @return a new project description + * @see IProject#getDescription() + * @see IProject#create(IProjectDescription, IProgressMonitor) + * @see IResource#copy(IProjectDescription, int, IProgressMonitor) + * @see IProject#move(IProjectDescription, boolean, IProgressMonitor) + */ + public IProjectDescription newProjectDescription(String projectName); + + /** + * Removes the given resource change listener from this workspace. Has no + * effect if an identical listener is not registered. + * + * @param listener the listener + * @see IResourceChangeListener + * @see #addResourceChangeListener(IResourceChangeListener) + */ + public void removeResourceChangeListener(IResourceChangeListener listener); + + /** + * Removes the workspace save participant for the given plug-in from this + * workspace. If no such participant is registered, no action is taken. + *

                          + * Once removed, the workspace save participant no longer actively + * participates in any future saves of this workspace. + *

                          + * + * @param plugin the plug-in + * @see ISaveParticipant + * @see #addSaveParticipant(Plugin, ISaveParticipant) + * @deprecated Use {@link #removeSaveParticipant(String)} instead + */ + @Deprecated + public void removeSaveParticipant(Plugin plugin); + + /** + * Removes the workspace save participant for the given plug-in from this + * workspace. If no such participant is registered, no action is taken. + *

                          + * Once removed, the workspace save participant no longer actively + * participates in any future saves of this workspace. + *

                          + * + * @param pluginId the unique identifier of the plug-in + * @see ISaveParticipant + * @see #addSaveParticipant(String, ISaveParticipant) + * @since 3.6 + */ + public void removeSaveParticipant(String pluginId); + + /** + * Runs the given action as an atomic workspace operation. + *

                          + * After running a method that modifies resources in the workspace, + * registered listeners receive after-the-fact notification of what just + * transpired, in the form of a resource change event. This method allows + * clients to call a number of methods that modify resources and only have + * resource change event notifications reported at the end of the entire + * batch. + *

                          + *

                          + * If this method is called outside the dynamic scope of another such call, + * this method runs the action and then reports a single resource change + * event describing the net effect of all changes done to resources by the + * action. + *

                          + *

                          + * If this method is called in the dynamic scope of another such call, this + * method simply runs the action. + *

                          + *

                          + * The supplied scheduling rule is used to determine whether this operation + * can be run simultaneously with workspace changes in other threads. If the + * scheduling rule conflicts with another workspace change that is currently + * running, the calling thread will be blocked until that change completes. + * If the action attempts to make changes to the workspace that were not + * specified in the scheduling rule, it will fail. If no scheduling rule is + * supplied, there are no scheduling restrictions for this operation. + * If a non-null scheduling rule is supplied, this operation + * must always support cancelation in the case where this operation becomes + * blocked by a long running background operation. + *

                          + *

                          + * The AVOID_UPDATE flag controls whether periodic resource change + * notifications should occur during the scope of this call. If this flag is + * specified, and no other threads modify the workspace concurrently, then + * all resource change notifications will be deferred until the end of this + * call. If this flag is not specified, the platform may decide to broadcast + * periodic resource change notifications during the scope of this call. + *

                          + *

                          + * Flags other than AVOID_UPDATE are ignored. + *

                          + * + * @param action the action to perform + * @param rule the scheduling rule to use when running this operation, or + * null if there are no scheduling restrictions for this + * operation. + * @param flags bit-wise or of flag constants (only AVOID_UPDATE is relevant + * here) + * @param monitor a progress monitor, or null if progress + * reporting is not desired. + * @exception CoreException if the operation failed. + * @exception OperationCanceledException if the operation is canceled. If a + * non-null scheduling rule is supplied, cancelation can occur + * even if no progress monitor is provided. + * + * @see #AVOID_UPDATE + * @see IResourceRuleFactory + * @since 3.11 + */ + public void run(ICoreRunnable action, ISchedulingRule rule, int flags, IProgressMonitor monitor) throws CoreException; + + /** + * Runs the given action as an atomic workspace operation. + *

                          + * This is a convenience method, fully equivalent to: + * + *

                          +	 * workspace.run(action, workspace.getRoot(), IWorkspace.AVOID_UPDATE, monitor);
                          +	 * 
                          + * + *

                          + * + * @param action the action to perform + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the operation failed. + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * + * @see #run(ICoreRunnable, ISchedulingRule, int, IProgressMonitor) + * @since 3.11 + */ + public void run(ICoreRunnable action, IProgressMonitor monitor) throws CoreException; + + /** + * Identical to {@link #run(ICoreRunnable, ISchedulingRule, int, IProgressMonitor)}. + * New code should use that method. + * @since 3.0 + */ + public void run(IWorkspaceRunnable action, ISchedulingRule rule, int flags, IProgressMonitor monitor) throws CoreException; + + /** + * Identical to {@link #run(ICoreRunnable, IProgressMonitor)}. + * New code should use that method. + */ + public void run(IWorkspaceRunnable action, IProgressMonitor monitor) throws CoreException; + + /** + * Saves this workspace's valuable state on disk. Consults with all + * registered plug-ins so that they can coordinate the saving of their + * persistent state as well. + *

                          + * The full parameter indicates whether a full save or a + * snapshot is being requested. Snapshots save the workspace information + * that is considered hard to be recomputed in the unlikely event of a + * crash. It includes parts of the workspace tree, workspace and projects + * descriptions, markers and sync information. Full saves are heavy weight + * operations which save the complete workspace state. + *

                          + *

                          + * To ensure that all outstanding changes to the workspace have been + * reported to interested parties prior to saving, a full save cannot be + * used within the dynamic scope of an IWorkspace.run + * invocation. Snapshots can be called any time and are interpreted by the + * workspace as a hint that a snapshot is required. The workspace will + * perform the snapshot when possible. Even as a hint, snapshots should only + * be called when necessary as they impact system performance. Although + * saving does not change the workspace per se, its execution is serialized + * like methods that write the workspace. + *

                          + *

                          + * The workspace is comprised of several different kinds of data with + * varying degrees of importance. The most important data, the resources + * themselves and their persistent properties, are written to disk + * immediately; other data are kept in volatile memory and only written to + * disk periodically; and other data are maintained in memory and never + * written out. The following table summarizes what gets saved when: + *

                            + *
                          • creating or deleting resource - immediately
                          • + *
                          • setting contents of file - immediately
                          • + *
                          • changes to project description - immediately
                          • + *
                          • session properties - never
                          • + *
                          • changes to persistent properties - immediately
                          • + *
                          • markers -save
                          • + *
                          • synchronizer info -save
                          • + *
                          • shape of the workspace resource tree -save
                          • + *
                          • list of active plug-ins - never
                          • + *
                          + * Resource-based plug-in also have data with varying degrees of importance. + * Each plug-in gets to decide the policy for protecting its data, either + * immediately, never, or at save time. For the latter, the + * plug-in coordinates its actions with the workspace (see + * ISaveParticipant for details). + *

                          + *

                          + * If the platform is shutdown (or crashes) after saving the workspace, any + * information written to disk by the last successful workspace + * save will be restored the next time the workspace is + * reopened for the next session. Naturally, information that is written to + * disk immediately will be as of the last time it was changed. + *

                          + *

                          + * The workspace provides a general mechanism for keeping concerned parties + * apprised of any and all changes to resources in the workspace ( + * IResourceChangeListener). It is even possible for a + * plug-in to find out about changes to resources that happen between + * workspace sessions (see IWorkspace.addSaveParticipant). + *

                          + *

                          + * At certain points during this method, the entire workspace resource tree + * must be locked to prevent resources from being changed (read access to + * resources is permitted). + *

                          + *

                          + * Implementation note: The execution sequence is as follows. + *

                            + *
                          • A long-term lock on the workspace is taken out to prevent further + * changes to workspace until the save is done.
                          • + *
                          • The list of saveable resource tree snapshots is initially empty. + *
                          • + *
                          • A different ISaveContext object is created for each + * registered workspace save participant plug-in, reflecting the kind of + * save (ISaveContext.getKind), the previous save number in + * which this plug-in actively participated, and the new save number (= + * previous save number plus 1).
                          • + *
                          • Each registered workspace save participant is sent + * prepareToSave(context), passing in its own context + * object. + *
                              + *
                            • Plug-in suspends all activities until further notice.
                            • + *
                            + * If prepareToSave fails (throws an exception), the problem + * is logged and the participant is marked as unstable.
                          • + *
                          • In dependent-before-prerequisite order, each registered workspace + * save participant is sent saving(context), passing in its + * own context object. + *
                              + *
                            • Plug-in decides whether it wants to actively participate in this + * save. The plug-in only needs to actively participate if some of its + * important state has changed since the last time it actively participated. + * If it does decide to actively participate, it writes its important state + * to a brand new file in its plug-in state area under a generated file name + * based on context.getStateNumber() and calls + * context.needStateNumber() to indicate that it has actively + * participated. If upon reactivation the plug-in will want a resource delta + * covering all changes between now and then, the plug-in should invoke + * context.needDelta() to request this now; otherwise, a + * resource delta for the intervening period will not be available on + * reactivation.
                            • + *
                            + * If saving fails (throws an exception), the problem is + * logged and the participant is marked as unstable.
                          • + *
                          • The plug-in save table contains an entry for each plug-in that has + * registered to participate in workspace saves at some time in the past + * (the list of plug-ins increases monotonically). Each entry records the + * save number of the last successful save in which that plug-in actively + * participated, and, optionally, a saved resource tree (conceptually, this + * is a complete tree; in practice, it is compressed into a special delta + * tree representation). A copy of the plug-in save table is made. Entries + * are created or modified for each registered plug-in to record the + * appropriate save number (either the previous save number, or the previous + * save number plus 1, depending on whether the participant was active and + * asked for a new number).
                          • + *
                          • The workspace tree, the modified copy of the plug-in save table, all + * markers, etc. and all saveable resource tree snapshots are written to + * disk as one atomic operation .
                          • + *
                          • The long-term lock on the workspace is released.
                          • + *
                          • If the atomic save succeeded: + *
                              + *
                            • The modified copy of the plug-in save table becomes the new plug-in + * save table.
                            • + *
                            • In prerequisite-before-dependent order, each registered workspace + * save participant is sent doneSaving(context), passing in + * its own context object. + *
                                + *
                              • Plug-in may perform clean up by deleting obsolete state files in its + * plug-in state area.
                              • + *
                              • Plug-in resumes its normal activities.
                              • + *
                              + * If doneSaving fails (throws an exception), the problem is + * logged and the participant is marked as unstable. (The state number in + * the save table is not rolled back just because of this instability.) + *
                            • + *
                            • The workspace save operation returns.
                            • + *
                            + *
                          • If it failed: + *
                              + *
                            • The workspace previous state is restored.
                            • + *
                            • In prerequisite-before-dependent order, each registered workspace + * save participant is sent rollback(context), passing in + * its own context object. + *
                                + *
                              • Plug-in may perform clean up by deleting newly-created but obsolete + * state file in its plug-in state area.
                              • + *
                              • Plug-in resumes its normal activities.
                              • + *
                              + * If rollback fails (throws an exception), the problem is + * logged and the participant is marked as unstable. (The state number in + * the save table is rolled back anyway.)
                            • + *
                            • The workspace save operation fails.
                            • + *
                            + *
                          • + *
                          + *

                          + *

                          + * After a full save, the platform can be shutdown. This will cause the + * Resources plug-in and all the other plug-ins to shutdown, without + * disturbing the saved workspace on disk. + *

                          + *

                          + * When the platform is later restarted, activating the Resources plug-in + * opens the saved workspace. This reads into memory the workspace's + * resource tree, plug-in save table, and saved resource tree snapshots + * (everything that was written to disk in the atomic operation above). + * Later, when a plug-in gets reactivated and registers to participate in + * workspace saves, it is handed back the info from its entry in the plug-in + * save table, if it has one. It gets back the number of the last save in + * which it actively participated and, possibly, a resource delta. + *

                          + *

                          + * The only source of long term garbage would come from a plug-in that never + * gets reactivated, or one that gets reactivated but fails to register for + * workspace saves. (There is no such problem with a plug-in that gets + * uninstalled; its easy enough to scrub its state areas and delete its + * entry in the plug-in save table.) + *

                          + * + * @param full true if this is a full save, and + * false if this is only a snapshot for protecting against + * crashes + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status that may contain warnings, such as the failure of an + * individual participant + * @exception CoreException if this method fails to save the state of this + * workspace. Reasons include: + *
                            + *
                          • The operation cannot be batched with others.
                          • + *
                          + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #addSaveParticipant(Plugin, ISaveParticipant) + */ + public IStatus save(boolean full, IProgressMonitor monitor) throws CoreException; + + /** + * Sets the workspace description. Its values are stored in the preference + * store. + * + * @param description the new workspace description. + * @see #getDescription() + * @exception CoreException if the method fails. Reasons include: + *
                            + *
                          • There was a problem setting the workspace description.
                          • + *
                          + */ + public void setDescription(IWorkspaceDescription description) throws CoreException; + + /** + * Returns a copy of the given set of natures sorted in prerequisite order. + * For each nature, it is guaranteed that all of its prerequisites will + * precede it in the resulting array. + * + *

                          + * Natures that are missing from the install or are involved in a + * prerequisite cycle are sorted arbitrarily. Duplicate nature IDs are + * removed, so the returned array may be smaller than the original. + *

                          + * + * @param natureIds a valid set of nature extension identifiers + * @return the set of nature Ids sorted in prerequisite order + * @see #validateNatureSet(String[]) + * @since 2.0 + */ + public String[] sortNatureSet(String[] natureIds); + + /** + * Advises that the caller intends to modify the contents of the given files + * in the near future and asks whether modifying all these files would be + * reasonable. The files must all exist. This method is used to give the VCM + * component an opportunity to check out (or otherwise prepare) the files if + * required. (It is provided in this component rather than in the UI so that + * "core" (i.e., head-less) clients can use it. Similarly, it is located + * outside the VCM component for the convenience of clients that must also + * operate in configurations without VCM.) + *

                          + *

                          + * A client (such as an editor) should perform a validateEdit + * on a file whenever it finds itself in the following position: (a) the + * file is marked read-only, and (b) the client believes it likely (not + * necessarily certain) that it will modify the file's contents at some + * point. A case in point is an editor that has a buffer opened on a file. + * When the user starts to dirty the buffer, the editor should check to see + * whether the file is read-only. If it is, it should call + * validateEdit, and can reasonably expect this call, when + * successful, to cause the file to become read-write. An editor should also + * be sensitive to a file becoming read-only again even after a successful + * validateEdit (e.g., due to the user checking in the file + * in a different view); the editor should again call + * validateEdit if the file is read-only before attempting to + * save the contents of the file. + *

                          + *

                          + * By passing a UI context, the caller indicates that the VCM component may + * contact the user to help decide how best to proceed. If no UI context is + * provided, the VCM component will make its decision without additional + * interaction with the user. If OK is returned, the caller can safely + * assume that all of the given files haven been prepared for modification + * and that there is good reason to believe that + * IFile.setContents (or appendContents) + * would be successful on any of them. If the result is not OK, modifying + * the given files might not succeed for the reason(s) indicated. + *

                          + *

                          + * If a shell is passed in as the context, the VCM component may bring up a + * dialogs to query the user or report difficulties; the shell should be + * used to parent any such dialogs; the caller may safely assume that the + * reasons for failure will have been made clear to the user. If + * {@link IWorkspace#VALIDATE_PROMPT} is passed + * as the context, this indicates that the caller does not have access to + * a UI context but would still like the user to be prompted if required. + * If null is passed, the user should not be contacted; any + * failures should be reported via the result; the caller may chose to + * present these to the user however they see fit. The ideal implementation + * of this method is transactional; no files would be affected unless the + * go-ahead could be given. (In practice, there may be no feasible way to + * ensure such changes get done atomically.) + *

                          + *

                          + * The method calls FileModificationValidator.validateEdit + * for the file modification validator (if provided by the VCM plug-in). + * When there is no file modification validator, this method returns a + * status with an IResourceStatus.READ_ONLY_LOCAL code if one + * of the files is read-only, and a status with an IStatus.OK + * code otherwise. + *

                          + *

                          + * This method may be called from any thread. If the UI context is used, it + * is the responsibility of the implementor of + * FileModificationValidator.validateEdit to interact with + * the UI context in an appropriate thread. + *

                          + * + * @param files the files that are to be modified; these files must all + * exist in the workspace + * @param context either {@link IWorkspace#VALIDATE_PROMPT}, + * or the org.eclipse.swt.widgets.Shell that is + * to be used to parent any dialogs with the user, or null if + * there is no UI context (declared as an Object to avoid any + * direct references on the SWT component) + * @return a status object that is OK if things are fine, + * otherwise a status describing reasons why modifying the given files is not + * reasonable. A status with a severity of CANCEL is returned + * if the validation was canceled, indicating the edit should not proceed. + * @see IResourceRuleFactory#validateEditRule(IResource[]) + * @since 2.0 + */ + public IStatus validateEdit(IFile[] files, Object context); + + /** + * Validates that the given resource will not (or would not, if the resource + * doesn't exist in the workspace yet) be filtered out from the workspace by + * its parent resource filters. + *

                          + * Note that if the resource or its parent doesn't exist yet in the workspace, + * it is possible that it will still be effectively filtered out once the resource + * and/or its parent is created, even though this method doesn't report it. + * + * But if this method reports the resource as filtered, even though it, or its + * parent, doesn't exist in the workspace yet, it means that the resource will + * be filtered out by its parent resource filters once it exists in the workspace. + *

                          + *

                          + * This method will return a status with severity IStatus.ERROR + * if the resource will be filtered out - removed - out of the workspace by + * its parent resource filters. + *

                          + *

                          + * Note: linked resources and virtual folders are never filtered out by their + * parent resource filters. + * + * @param resource the resource to validate the location for + * @return a status object with code IStatus.OK if the given + * resource is not filtered by its parent resource filters, otherwise a status + * object with severity IStatus.ERROR indicating that it will + * @see IStatus#OK + * @since 3.6 + */ + public IStatus validateFiltered(IResource resource); + + /** + * Validates the given path as the location of the given resource on disk. + * The path must be either an absolute file system path, or a relative path + * whose first segment is the name of a defined workspace path variable. In + * addition to the restrictions for paths in general (see IPath. + * isValidPath), + * a link location must also obey the following rules: + *

                            + *
                          • must not overlap with the platform's metadata directory
                          • + *
                          • must not be the same as or a parent of the root directory of the + * project the linked resource is contained in
                          • + *
                          + *

                          + * This method also checks that the given resource can legally become a + * linked resource. This includes the following restrictions: + *

                            + *
                          • must have a project as its immediate parent
                          • + *
                          • project natures and the team hook may disallow linked resources on + * projects they are associated with
                          • + *
                          • the global workspace preference to disable linking, + * ResourcesPlugin.PREF_DISABLE_LINKING must not be set to + * "true"
                          • . + *
                          + *

                          + *

                          + * This method will return a status with severity IStatus.ERROR + * if the location does not obey the above rules. Also, this method will + * return a status with severity IStatus.WARNING if the + * location overlaps the location of any existing resource in the workspace. + *

                          + *

                          + * Note: this method does not consider whether files or directories exist in + * the file system at the specified path. + * + * @param resource the resource to validate the location for + * @param location the location of the linked resource contents on disk + * @return a status object with code IStatus.OK if the given + * location is valid as the linked resource location, otherwise a status + * object with severity IStatus.WARNING or + * IStatus.ERROR indicating what is wrong with the location + * @see IStatus#OK + * @see ResourcesPlugin#PREF_DISABLE_LINKING + * @since 2.1 + */ + public IStatus validateLinkLocation(IResource resource, IPath location); + + /** + * Validates the given {@link URI} as the location of the given resource on disk. + * The location must be either an absolute URI, or a relative URI + * whose first segment is the name of a defined workspace path variable. + * A link location must obey the following rules: + *

                            + *
                          • must not overlap with the platform's metadata directory
                          • + *
                          • must not be the same as or a parent of the root directory of the + * project the linked resource is contained in
                          • + *
                          + *

                          + * This method also checks that the given resource can legally become a + * linked resource. This includes the following restrictions: + *

                            + *
                          • must have a project as its immediate parent
                          • + *
                          • project natures and the team hook may disallow linked resources on + * projects they are associated with
                          • + *
                          • the global workspace preference to disable linking, + * ResourcesPlugin.PREF_DISABLE_LINKING must not be set to + * "true"
                          • . + *
                          + *

                          + *

                          + * This method will return a status with severity IStatus.ERROR + * if the location does not obey the above rules. Also, this method will + * return a status with severity IStatus.WARNING if the + * location overlaps the location of any existing resource in the workspace. + *

                          + *

                          + * Note: this method does not consider whether files or directories exist in + * the file system at the specified location. + * + * @param resource the resource to validate the location for + * @param location the location of the linked resource contents in some file system + * @return a status object with code IStatus.OK if the given + * location is valid as the linked resource location, otherwise a status + * object with severity IStatus.WARNING or + * IStatus.ERROR indicating what is wrong with the location + * @see IStatus#OK + * @see ResourcesPlugin#PREF_DISABLE_LINKING + * @since 3.2 + */ + public IStatus validateLinkLocationURI(IResource resource, URI location); + + /** + * Validates the given string as the name of a resource valid for one of the + * given types. + *

                          + * In addition to the basic restrictions on paths in general (see + * {@link IPath#isValidSegment(String)}), a resource name must also not + * contain any characters or substrings that are not valid on the file system + * on which workspace root is located. In addition, the names "." and ".." + * are reserved due to their special meaning in file system paths. + *

                          + *

                          + * This validation check is done automatically as a resource is created (but + * not when the resource handle is constructed); this means that any + * resource that exists can be safely assumed to have a valid name and path. + * Note that the name of the workspace root resource is inherently invalid. + *

                          + * + * @param segment the name segment to be checked + * @param typeMask bitwise-or of the resource type constants ( + * FILE,FOLDER,PROJECT or + * ROOT) indicating expected resource type(s) + * @return a status object with code IStatus.OK if the given + * string is valid as a resource name, otherwise a status object indicating + * what is wrong with the string + * @see IResource#PROJECT + * @see IResource#FOLDER + * @see IResource#FILE + * @see IStatus#OK + */ + public IStatus validateName(String segment, int typeMask); + + /** + * Validates that each of the given natures exists, and that all nature + * constraints are satisfied within the given set. + *

                          + * The following conditions apply to validation of a set of natures: + *

                            + *
                          • all natures in the set exist in the plug-in registry + *
                          • all prerequisites of each nature are present in the set + *
                          • there are no cycles in the prerequisite graph of the set + *
                          • there are no two natures in the set that specify one-of-nature + * inclusion in the same group. + *
                          • there are no two natures in the set with the same id + *
                          + *

                          + *

                          + * An empty nature set is always valid. + *

                          + * + * @param natureIds an array of nature extension identifiers + * @return a status object with code IStatus.OK if the given + * set of natures is valid, otherwise a status object indicating what is + * wrong with the set + * @since 2.0 + */ + public IStatus validateNatureSet(String[] natureIds); + + /** + * Validates the given string as a path for a resource of the given type(s). + *

                          + * In addition to the restrictions for paths in general (see + * IPath.isValidPath), a resource path should also obey the + * following rules: + *

                            + *
                          • a resource path should be an absolute path with no device id + *
                          • its segments should be valid names according to + * validateName + *
                          • a path for the workspace root must be the canonical root path + *
                          • a path for a project should have exactly 1 segment + *
                          • a path for a file or folder should have more than 1 segment + *
                          • the first segment should be a valid project name + *
                          • the second through penultimate segments should be valid folder names + *
                          • the last segment should be a valid name of the given type + *
                          + *

                          + *

                          + * Note: this method does not consider whether a resource at the specified + * path exists. + *

                          + *

                          + * This validation check is done automatically as a resource is created (but + * not when the resource handle is constructed); this means that any + * resource that exists can be safely assumed to have a valid name and path. + *

                          + * + * @param path the path string to be checked + * @param typeMask bitwise-or of the resource type constants ( + * FILE,FOLDER,PROJECT, or + * ROOT) indicating expected resource type(s) + * @return a status object with code IStatus.OK if the given + * path is valid as a resource path, otherwise a status object indicating + * what is wrong with the string + * @see IResource#PROJECT + * @see IResource#FOLDER + * @see IResource#FILE + * @see IStatus#OK + * @see IResourceStatus#getPath() + */ + public IStatus validatePath(String path, int typeMask); + + /** + * Validates the given path as the location of the given project on disk. + * The path must be either an absolute file system path, or a relative path + * whose first segment is the name of a defined workspace path variable. In + * addition to the restrictions for paths in general (see IPath. + * isValidPath), + * a location path should also obey the following rules: + *
                            + *
                          • must not be the same as another open or closed project
                          • + *
                          • must not occupy the default location for any project, whether existing or not
                          • + *
                          • must not be the same as or a parent of the platform's working directory
                          • + *
                          • must not be the same as or a child of the location of any existing + * linked resource in the given project
                          • + *
                          + *

                          + *

                          + * Note: this method does not consider whether files or directories exist in + * the file system at the specified path. + *

                          + * + * @param project the project to validate the location for, can be null + * if non default project location is validated + * @param location the location of the project contents on disk, or null + * if the default project location is used + * @return a status object with code IStatus.OK if the given + * location is valid as the project content location, otherwise a status + * object indicating what is wrong with the location + * @see IProjectDescription#getLocationURI() + * @see IProjectDescription#setLocation(IPath) + * @see IStatus#OK + */ + public IStatus validateProjectLocation(IProject project, IPath location); + + /** + * Validates the given URI as the location of the given project. + * The location must be either an absolute URI, or a relative URI + * whose first segment is the name of a defined workspace path variable. + * A project location must obey the following rules: + *
                            + *
                          • must not be the same as another open or closed project
                          • + *
                          • must not occupy the default location for any project, whether existing or not
                          • + *
                          • must not be the same as or a parent of the platform's working directory
                          • + *
                          • must not be the same as or a child of the location of any existing + * linked resource in the given project
                          • + *
                          + *

                          + *

                          + * Note: this method does not consider whether files or directories exist in + * the file system at the specified path. + *

                          + * + * @param project the project to validate the location for, can be null + * if non default project location is validated + * @param location the location of the project contents on disk, or null + * if the default project location is used + * @return a status object with code IStatus.OK if the given + * location is valid as the project content location, otherwise a status + * object indicating what is wrong with the location + * @see IProjectDescription#getLocationURI() + * @see IProjectDescription#setLocationURI(URI) + * @see IStatus#OK + * @since 3.2 + */ + public IStatus validateProjectLocationURI(IProject project, URI location); + + /** + * Returns the path variable manager for this workspace. + * + * @return the path variable manager + * @see IPathVariableManager + * @since 2.1 + */ + public IPathVariableManager getPathVariableManager(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java new file mode 100644 index 0000000000..84a60cfd69 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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; + +/** + * A workspace description represents the workspace preferences. It can be + * used to query the current preferences and set new ones. The workspace + * preference values are stored in the preference store and are also accessible + * via the preference mechanism. Constants for the preference keys are found + * on the ResourcesPlugin class. + * + * @see IWorkspace#getDescription() + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see IWorkspace#newProjectDescription(String) + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IWorkspaceDescription { + /** + * Returns the order in which projects in the workspace should be built. + * The returned value is null if the workspace's default build + * order is being used. + * + * @return the names of projects in the order they will be built, + * or null if the default build order should be used + * @see #setBuildOrder(String[]) + * @see ResourcesPlugin#PREF_BUILD_ORDER + */ + public String[] getBuildOrder(); + + /** + * Returns the maximum length of time, in milliseconds, a file state should be + * kept in the local history. This setting is ignored by the workspace when + * isApplyFileStatePolicy() returns false. + * + * @return the maximum time a file state should be kept in the local history + * represented in milliseconds + * @see #setFileStateLongevity(long) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_FILE_STATE_LONGEVITY + */ + public long getFileStateLongevity(); + + /** + * Returns the maximum number of times that the workspace should rebuild when + * builders affect projects that have already been built. + * + * @return the maximum number of times that the workspace should rebuild when + * builders affect projects that have already been built. + * @see #setMaxBuildIterations(int) + * @see ResourcesPlugin#PREF_MAX_BUILD_ITERATIONS + * @since 2.1 + */ + public int getMaxBuildIterations(); + + /** + * Returns the maximum number of states per file that can be stored in the local history. + * This setting is ignored by the workspace when isApplyFileStatePolicy() + * returns false. + * + * @return the maximum number of states per file that can be stored in the local history + * @see #setMaxFileStates(int) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_MAX_FILE_STATES + */ + public int getMaxFileStates(); + + /** + * Returns the maximum permitted size of a file, in bytes, to be stored in the + * local history. This setting is ignored by the workspace when + * isApplyFileStatePolicy() returns false. + * + * @return the maximum permitted size of a file to be stored in the local history + * @see #setMaxFileStateSize(long) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_MAX_FILE_STATE_SIZE + */ + public long getMaxFileStateSize(); + + /** + * Returns whether file states are discarded according to the policy specified by + * setFileStateLongevity(long), setMaxFileStates(int) + * and setMaxFileStateSize(long) methods. + * + * @return true if file states are removed due to the policy, + * false otherwise + * @see #setApplyFileStatePolicy(boolean) + * @see #setFileStateLongevity(long) + * @see #setMaxFileStates(int) + * @see #setMaxFileStateSize(long) + * @see ResourcesPlugin#PREF_APPLY_FILE_STATE_POLICY + * @since 3.6 + */ + public boolean isApplyFileStatePolicy(); + + /** + * Returns the interval between automatic workspace snapshots. + * + * @return the amount of time in milliseconds between automatic workspace snapshots + * @see #setSnapshotInterval(long) + * @see ResourcesPlugin#PREF_SNAPSHOT_INTERVAL + * @since 2.0 + */ + public long getSnapshotInterval(); + + /** + * Returns whether this workspace performs autobuilds. + * + * @return true if autobuilding is on, otherwise + * false + * @see #setAutoBuilding(boolean) + * @see ResourcesPlugin#PREF_AUTO_BUILDING + */ + public boolean isAutoBuilding(); + + /** + * Records whether this workspace performs autobuilds. + *

                          + * When autobuild is on, any changes made to a project and its + * resources automatically triggers an incremental build of the workspace. + *

                          + *

                          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                          + * + * @param value true to turn on autobuilding, + * and false to turn it off + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #isAutoBuilding() + * @see ResourcesPlugin#PREF_AUTO_BUILDING + */ + public void setAutoBuilding(boolean value); + + /** + * Sets the order in which projects in the workspace should be built. + * Projects not named in this list are built in a default order defined + * by the workspace. Set this value to null to use the + * default ordering for all projects. Projects not named in the list are + * built in unspecified order after all ordered projects. + *

                          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                          + * + * @param value the names of projects in the order in which they are built, + * or null to use the workspace's default order for all projects + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getBuildOrder() + * @see ResourcesPlugin#PREF_BUILD_ORDER + */ + public void setBuildOrder(String[] value); + + /** + * Sets the maximum time, in milliseconds, a file state should be kept in the + * local history. This setting is ignored by the workspace when setApplyFileStatePolicy(boolean) + * is set to false. + *

                          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                          + * + * @param time the maximum number of milliseconds a file state should be + * kept in the local history + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getFileStateLongevity() + * @see #setApplyFileStatePolicy(boolean) + * @see ResourcesPlugin#PREF_FILE_STATE_LONGEVITY + */ + public void setFileStateLongevity(long time); + + /** + * Sets the maximum number of times that the workspace should rebuild when + * builders affect projects that have already been built. + *

                          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                          + * + * @param number the maximum number of times that the workspace should rebuild + * when builders affect projects that have already been built. + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getMaxBuildIterations() + * @see ResourcesPlugin#PREF_MAX_BUILD_ITERATIONS + * @since 2.1 + */ + public void setMaxBuildIterations(int number); + + /** + * Sets the maximum number of states per file that can be stored in the local history. + * If the maximum number is reached, older states are removed in favor of + * new ones. This setting is ignored by the workspace when setApplyFileStatePolicy(boolean) + * is set to false. + *

                          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                          + * + * @param number the maximum number of states per file that can be stored in the local history + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getMaxFileStates() + * @see #setApplyFileStatePolicy(boolean) + * @see ResourcesPlugin#PREF_MAX_FILE_STATES + */ + public void setMaxFileStates(int number); + + /** + * Sets the maximum permitted size of a file, in bytes, to be stored in the + * local history. This setting is ignored by the workspace when setApplyFileStatePolicy(boolean) + * is set to false. + *

                          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                          + * + * @param size the maximum permitted size of a file to be stored in the local history + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getMaxFileStateSize() + * @see #setApplyFileStatePolicy(boolean) + * @see ResourcesPlugin#PREF_MAX_FILE_STATE_SIZE + */ + public void setMaxFileStateSize(long size); + + /** + * Sets whether file states are discarded according to the policy specified by + * setFileStateLongevity(long), setMaxFileStates(int) + * and setMaxFileStateSize(long) methods. + *

                          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                          + * + * @param apply true if file states are removed due to the policy, + * false otherwise + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #setFileStateLongevity(long) + * @see #setMaxFileStates(int) + * @see #setMaxFileStateSize(long) + * @see #isApplyFileStatePolicy() + * @see ResourcesPlugin#PREF_APPLY_FILE_STATE_POLICY + * @since 3.6 + */ + public void setApplyFileStatePolicy(boolean apply); + + /** + * Sets the interval between automatic workspace snapshots. The new interval + * will only take effect after the next snapshot. + *

                          + * Users must call IWorkspace.setDescription before changes + * made to this description take effect. + *

                          + * + * @param delay the amount of time in milliseconds between automatic workspace snapshots + * @see IWorkspace#setDescription(IWorkspaceDescription) + * @see #getSnapshotInterval() + * @see ResourcesPlugin#PREF_SNAPSHOT_INTERVAL + * @since 2.0 + */ + public void setSnapshotInterval(long delay); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java new file mode 100644 index 0000000000..d11b6d0507 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRoot.java @@ -0,0 +1,402 @@ +/******************************************************************************* + * Copyright (c) 2000, 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.resources; + +import java.net.URI; +import org.eclipse.core.runtime.*; + +/** + * A root resource represents the top of the resource hierarchy in a workspace. + * There is exactly one root in a workspace. The root resource has the following + * behavior: + *
                            + *
                          • It cannot be moved or copied
                          • + *
                          • It always exists.
                          • + *
                          • Deleting the root deletes all of the children under the root but leaves the root itself
                          • + *
                          • It is always local.
                          • + *
                          • It is never a phantom.
                          • + *
                          + *

                          + * Workspace roots implement the IAdaptable interface; + * extensions are managed by the platform's adapter manager. + *

                          + * + * @see Platform#getAdapterManager() + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IWorkspaceRoot extends IContainer, IAdaptable { + + /** + * Deletes everything in the workspace except the workspace root resource + * itself. + *

                          + * This is a convenience method, fully equivalent to: + *

                          +	 *   delete(
                          +	 *     (deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT )
                          +	 *        | (force ? FORCE : IResource.NONE),
                          +	 *     monitor);
                          +	 * 
                          + *

                          + *

                          + * This method changes resources; these changes will be reported + * in a subsequent resource change event. + *

                          + *

                          + * This method is long-running; progress and cancellation are provided + * by the given progress monitor. + *

                          + * + * @param deleteContent a flag controlling how whether content is + * aggressively deleted + * @param force a flag controlling whether resources that are not + * in sync with the local file system will be tolerated + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                            + *
                          • A project could not be deleted.
                          • + *
                          • A project's contents could not be deleted.
                          • + *
                          • Resource changes are disallowed during certain types of resource change + * event notification. See IResourceChangeEvent for more details.
                          • + *
                          + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the handles to all the resources (workspace root, project, + * folder) in the workspace which are mapped to the given path in the local + * file system. Returns an empty array if there are none. + *

                          + * If the path maps to the platform working location, the returned object + * will be a single element array consisting of an object of type + * ROOT. + *

                          + *

                          + * If the path maps to a project, the resulting array will contain a + * resource of type PROJECT, along with any linked folders that + * share the same location. Otherwise the resulting array will contain + * folders (type FOLDER). + *

                          + *

                          + * The path should be absolute; a relative path will be treated as absolute. + * The path segments need not be valid names; a trailing separator is + * ignored. The resulting resources may not currently exist. + *

                          + *

                          + * The result will omit team private members and hidden resources. The + * result will omit resources within team private members or hidden + * containers. + *

                          + *

                          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                          + * + * @param location + * a path in the local file system + * @return the corresponding containers in the workspace, or an empty array + * if none + * @since 2.1 + * @deprecated use {@link #findContainersForLocationURI(URI)} instead + */ + @Deprecated + public IContainer[] findContainersForLocation(IPath location); + + /** + * Returns the handles to all the resources (workspace root, project, + * folder) in the workspace which are mapped to the given URI. Returns an + * empty array if there are none. + *

                          + * If the path maps to the platform working location, the returned object + * will be a single element array consisting of an object of type + * ROOT. + *

                          + *

                          + * If the path maps to a project, the resulting array will contain a + * resource of type PROJECT, along with any linked folders that + * share the same location. Otherwise the resulting array will contain + * folders (type FOLDER). + *

                          + *

                          + * The URI must be absolute; its segments need not be valid names; a + * trailing separator is ignored. The resulting resources may not currently + * exist. + *

                          + *

                          + * The result will omit team private members and hidden resources. The + * result will omit resources within team private member sor hidden + * containers. + *

                          + *

                          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                          + *

                          + * This is a convenience method, fully equivalent to + * findContainersForLocationURI(location, IResource.NONE). + *

                          + * + * @param location + * a URI path into some file system + * @return the corresponding containers in the workspace, or an empty array + * if none + * @since 3.2 + */ + public IContainer[] findContainersForLocationURI(URI location); + + /** + * Returns the handles to all the resources (workspace root, project, + * folder) in the workspace which are mapped to the given URI. Returns an + * empty array if there are none. + *

                          + * If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the + * member flags, team private members will be included along with the + * others. If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not + * specified (recommended), the result will omit any team private member + * resources. + *

                          + *

                          + * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, + * hidden members will be included along with the others. If the + * {@link #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 + * a URI path into some file system + * @param memberFlags + * bit-wise or of member flag constants ( + * {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} and {@link #INCLUDE_HIDDEN}) + * indicating which members are of interest + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 3.5 + */ + public IContainer[] findContainersForLocationURI(URI location, int memberFlags); + + /** + * Returns the handles of all files that are mapped to the given path in the + * local file system. Returns an empty array if there are none. The path + * should be absolute; a relative path will be treated as absolute. The path + * segments need not be valid names. The resulting files may not currently + * exist. + *

                          + * The result will omit any team private member and hidden resources. The + * result will omit resources within team private member or hidden + * containers. + *

                          + *

                          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                          + * + * @param location + * a path in the local file system + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 2.1 + * @deprecated use {@link #findFilesForLocationURI(URI)} instead + */ + @Deprecated + public IFile[] findFilesForLocation(IPath location); + + /** + * Returns the handles of all files that are mapped to the given URI. + * Returns an empty array if there are none. The URI must be absolute; its + * path segments need not be valid names. The resulting files may not + * currently exist. + *

                          + * The result will omit any team private member and hidden resources. The + * result will omit resources within team private member or hidden + * containers. + *

                          + *

                          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                          + *

                          + * This is a convenience method, fully equivalent to + * findFilesForLocationURI(location, IResource.NONE). + *

                          + * + * @param location + * a URI path into some file system + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 3.2 + */ + public IFile[] findFilesForLocationURI(URI location); + + /** + * Returns the handles of all files that are mapped to the given URI. + * Returns an empty array if there are none. The URI must be absolute; its + * path segments need not be valid names. The resulting files may not + * currently exist. + *

                          + * If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified in the + * member flags, team private members will be included along with the + * others. If the {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} flag is not + * specified (recommended), the result will omit any team private member + * resources. + *

                          + *

                          + * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, + * hidden members will be included along with the others. If the + * {@link #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 + * a URI path into some file system + * @param memberFlags + * bit-wise or of member flag constants ( + * {@link #INCLUDE_TEAM_PRIVATE_MEMBERS} and {@link #INCLUDE_HIDDEN}) + * indicating which members are of interest + * @return the corresponding files in the workspace, or an empty array if + * none + * @since 3.5 + */ + public IFile[] findFilesForLocationURI(URI location, int memberFlags); + + /** + * Returns a handle to the workspace root, project or folder + * which is mapped to the given path + * in the local file system, or null if none. + * If the path maps to the platform working location, the returned object + * will be of type ROOT. If the path maps to a + * project, the resulting object will be + * of type PROJECT; otherwise the resulting object + * will be a folder (type FOLDER). + * The path should be absolute; a relative path will be treated as + * absolute. The path segments need not be valid names; a trailing separator is ignored. + * The resulting resource may not currently exist. + *

                          + * This method returns null when the given file system location is not equal to + * or under the location of any existing project in the workspace, or equal to the + * location of the platform working location. + *

                          + *

                          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                          + *

                          + * This method prefers a container whose path has a minimal number of segments. + * I.e. a container in a nested project is preferred over a container in an enclosing project. + *

                          + *

                          + * Warning: This method ignores linked resources and their children. Since + * linked resources may overlap other resources, a unique mapping from a + * file system location to a single resource is not guaranteed. To find all + * resources for a given location, including linked resources, use the method + * findContainersForLocation. + *

                          + * + * @param location a path in the local file system + * @return the corresponding project or folder in the workspace, + * or null if none + */ + public IContainer getContainerForLocation(IPath location); + + /** + * Returns a handle to the file which is mapped to the given path + * in the local file system, or null if none. + * The path should be absolute; a relative path will be treated as + * absolute. The path segments need not be valid names. + * The resulting file may not currently exist. + *

                          + * This method returns null when the given file system location is not under + * the location of any existing project in the workspace. + *

                          + *

                          + * The result will also omit resources that are explicitly excluded + * from the workspace according to existing resource filters. + *

                          + *

                          + * This method prefers a file whose path has a minimal number of segments. + * I.e. a file in a nested project is preferred over a file in an enclosing project. + *

                          + *

                          + * Warning: This method ignores linked resources and their children. Since + * linked resources may overlap other resources, a unique mapping from a + * file system location to a single resource is not guaranteed. To find all + * resources for a given location, including linked resources, use the method + * findFilesForLocation. + *

                          + * + * @param location a path in the local file system + * @return the corresponding file in the workspace, + * or null if none + */ + public IFile getFileForLocation(IPath location); + + /** + * Returns a handle to the project resource with the given name + * which is a child of this root. The given name must be a valid + * path segment as defined by {@link IPath#isValidSegment(String)}. + *

                          + * Note: This method deals exclusively with resource handles, + * independent of whether the resources exist in the workspace. + * With the exception of validating that the name is a valid path segment, + * validation checking of the project name is not done + * when the project handle is constructed; rather, it is done + * automatically as the project is created. + *

                          + * + * @param name the name of the project + * @return a project resource handle + * @see #getProjects() + */ + public IProject getProject(String name); + + /** + * Returns the collection of projects which exist under this root. + * The projects can be open or closed. + *

                          + * This is a convenience method, fully equivalent to getProjects(IResource.NONE). + * Hidden projects are not included. + *

                          + * @return an array of projects + * @see #getProject(String) + * @see IResource#isHidden() + */ + public IProject[] getProjects(); + + /** + * Returns the collection of projects which exist under this root. + * The projects can be open or closed. + *

                          + * If the {@link #INCLUDE_HIDDEN} flag is specified in the member flags, hidden + * projects will be included along with the others. If the {@link #INCLUDE_HIDDEN} flag + * is not specified (recommended), the result will omit any hidden projects. + *

                          + * + * @param memberFlags bit-wise or of member flag constants indicating which + * projects are of interest (only {@link #INCLUDE_HIDDEN} is currently applicable) + * @return an array of projects + * @see #getProject(String) + * @see IResource#isHidden() + * @since 3.4 + */ + public IProject[] getProjects(int memberFlags); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java new file mode 100644 index 0000000000..513604abfe --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.runtime.*; + +/** + * This interface is structurally equivalent to {@link ICoreRunnable}. New code should use + * {@link ICoreRunnable} instead of {@code IWorkspaceRunnable}. + *

                          + * Clients may implement this interface. + *

                          + * @see IWorkspace#run(ICoreRunnable, IProgressMonitor) + */ +public interface IWorkspaceRunnable extends ICoreRunnable { + /** + * @param monitor a progress monitor, or {@code null} if progress reporting and + * cancellation are not desired. The monitor is only valid for the duration + * of the invocation of this method. Callers may call {@link IProgressMonitor#done()} + * after this method returns or throws an exception, but this is not strictly + * required. + * @exception CoreException if this operation fails + * @exception OperationCanceledException if this operation is canceled + */ + @Override + public void run(IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java new file mode 100644 index 0000000000..ed5654de41 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java @@ -0,0 +1,472 @@ +/******************************************************************************* + * 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 + * Anton Leherbauer (Wind River) - [198591] Allow Builder to specify scheduling rule + * Anton Leherbauer (Wind River) - [305858] Allow Builder to return null rule + * James Blackburn (Broadcom) - [306822] Provide Context for Builder getRule() + * Broadcom Corporation - build configurations and references + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.util.Map; +import org.eclipse.core.internal.events.InternalBuilder; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * The abstract base class for all incremental project builders. This class + * provides the infrastructure for defining a builder and fulfills the contract + * specified by the org.eclipse.core.resources.builders standard + * extension point. + *

                          + * All builders must subclass this class according to the following guidelines: + *

                            + *
                          • must re-implement at least build
                          • + *
                          • may implement other methods
                          • + *
                          • must supply a public, no-argument constructor
                          • + *
                          + * On creation, the setInitializationData method is called with + * any parameter data specified in the declaring plug-in's manifest. + */ +public abstract class IncrementalProjectBuilder extends InternalBuilder implements IExecutableExtension { + /** + * Build kind constant (value 6) indicating a full build request. A full + * build discards all previously built state and builds all resources again. + * Resource deltas are not applicable for this kind of build. + *

                          + * Note: If there is no previous delta, a request for {@link #INCREMENTAL_BUILD} + * or {@link #AUTO_BUILD} will result in the builder being called with {@link #FULL_BUILD} + * build kind. + *

                          + * + * @see IProject#build(int, IProgressMonitor) + * @see IProject#build(int, String, Map, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + */ + public static final int FULL_BUILD = 6; + /** + * Build kind constant (value 9) indicating an automatic build request. When + * autobuild is turned on, these builds are triggered automatically whenever + * resources change. Apart from the method by which autobuilds are triggered, + * they otherwise operate like an incremental build. + * + * @see IWorkspaceDescription#setAutoBuilding(boolean) + * @see IWorkspace#isAutoBuilding() + */ + public static final int AUTO_BUILD = 9; + /** + * Build kind constant (value 10) indicating an incremental build request. + * Incremental builds use an {@link IResourceDelta} that describes what + * resources have changed since the last build. The builder calculates + * what resources are affected by the delta, and rebuilds the affected resources. + * + * @see IProject#build(int, IProgressMonitor) + * @see IProject#build(int, String, Map, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + */ + public static final int INCREMENTAL_BUILD = 10; + /** + * Build kind constant (value 15) indicating a clean build request. A clean + * build discards any additional state that has been computed as a result of + * previous builds, and returns the project to a clean slate. Resource + * deltas are not applicable for this kind of build. + * + * @see IProject#build(int, IProgressMonitor) + * @see IProject#build(int, String, Map, IProgressMonitor) + * @see IWorkspace#build(int, IProgressMonitor) + * @see #clean(IProgressMonitor) + * @since 3.0 + */ + public static final int CLEAN_BUILD = 15; + + /** + * Runs this builder in the specified manner. Subclasses should implement + * this method to do the processing they require. + *

                          + * If the build kind is {@link #INCREMENTAL_BUILD} or + * {@link #AUTO_BUILD}, the getDelta method can be + * used during the invocation of this method to obtain information about + * what changes have occurred since the last invocation of this method. Any + * resource delta acquired is valid only for the duration of the invocation + * of this method. A {@link #FULL_BUILD} has no associated build delta. + *

                          + *

                          + * After completing a build, this builder may return a list of projects for + * which it requires a resource delta the next time it is run. This + * builder's project is implicitly included and need not be specified. The + * build mechanism will attempt to maintain and compute deltas relative to + * the identified projects when asked the next time this builder is run. + * Builders must re-specify the list of interesting projects every time they + * are run as this is not carried forward beyond the next build. Projects + * mentioned in return value but which do not exist will be ignored and no + * delta will be made available for them. + *

                          + *

                          + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. All builders should report their progress and + * honor cancel requests in a timely manner. Cancelation requests should be + * propagated to the caller by throwing + * OperationCanceledException. + *

                          + *

                          + * All builders should try to be robust in the face of trouble. In + * situations where failing the build by throwing CoreException + * is the only option, a builder has a choice of how best to communicate the + * problem back to the caller. One option is to use the + * {@link IResourceStatus#BUILD_FAILED} status code along with a suitable message; + * another is to use a {@link MultiStatus} containing finer-grained problem + * diagnoses. + *

                          + * + * @param kind the kind of build being requested. Valid values are + *
                            + *
                          • {@link #FULL_BUILD} - indicates a full build.
                          • + *
                          • {@link #INCREMENTAL_BUILD}- indicates an incremental build.
                          • + *
                          • {@link #AUTO_BUILD} - indicates an automatically triggered + * incremental build (autobuilding on).
                          • + *
                          + * @param args a table of builder-specific arguments keyed by argument name + * (key type: String, value type: String); + * null is equivalent to an empty map + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @return the list of projects for which this builder would like deltas the + * next time it is run or null if none + * @exception CoreException if this build fails. + * @see IProject#build(int, String, Map, IProgressMonitor) + */ + @Override + protected abstract IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException; + + /** + * Clean is an opportunity for a builder to discard any additional state that has + * been computed as a result of previous builds. It is recommended that builders + * override this method to delete all derived resources created by previous builds, + * and to remove all markers of type {@link IMarker#PROBLEM} that + * were created by previous invocations of the builder. The platform will + * take care of discarding the builder's last built state (there is no need + * to call forgetLastBuiltState). + *

                          + *

                          + * This method is called as a result of invocations of + * IWorkspace.build or IProject.build where + * the build kind is {@link #CLEAN_BUILD}. + *

                          + * This default implementation does nothing. Subclasses may override. + *

                          + * This method is long-running; progress and cancellation are provided by + * the given progress monitor. All builders should report their progress and + * honor cancel requests in a timely manner. Cancelation requests should be + * propagated to the caller by throwing + * OperationCanceledException. + *

                          + * + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @exception CoreException if this build fails. + * @see IWorkspace#build(int, IProgressMonitor) + * @see #CLEAN_BUILD + * @since 3.0 + */ + @Override + protected void clean(IProgressMonitor monitor) throws CoreException { + //default implementation does nothing + //thwart compiler warning + } + + /** + * Requests that this builder forget any state it may be retaining regarding + * previously built states. Typically this means that the next time the + * builder runs, it will have to do a full build since it does not have any + * state upon which to base an incremental build. + * This supersedes a call to {@link #rememberLastBuiltState()}. + */ + @Override + public final void forgetLastBuiltState() { + super.forgetLastBuiltState(); + } + + /** + * Requests that this builder remember any build invocation specific state. + * This means that the next time the builder runs, it will receive a delta + * which includes changes reported in the current {@link #getDelta(IProject)}. + *

                          + * This can be used to indicate that a builder didn't run, even though there + * are changes, and the builder wishes that the delta be preserved until its + * next invocation. + *

                          + * This is superseded by a call to {@link #forgetLastBuiltState()}. + * @since 3.7 + */ + @Override + public final void rememberLastBuiltState() { + super.rememberLastBuiltState(); + } + + /** + * Returns the build command associated with this builder. The returned + * command may or may not be in the build specification for the project + * on which this builder operates. + *

                          + * Any changes made to the returned command will only take effect if + * the modified command is installed on a project build spec. + *

                          + * + * @see IProjectDescription#setBuildSpec(ICommand []) + * @see IProject#setDescription(IProjectDescription, int, IProgressMonitor) + * @since 3.1 + */ + @Override + public final ICommand getCommand() { + return super.getCommand(); + } + + /** + * Returns the resource delta recording the changes in the given project + * since the last time this builder was run. null is returned + * if no such delta is available. An empty delta is returned if no changes + * have occurred, or if deltas are not applicable for the current build kind. + * If null is returned, clients should assume + * that unspecified changes have occurred and take the appropriate action. + *

                          + * The system reserves the right to trim old state in an effort to conserve + * space. As such, callers should be prepared to receive null + * even if they previously requested a delta for a particular project by + * returning that project from a build call. + *

                          + *

                          + * A non- null delta will only be supplied for the given + * project if either the result returned from the previous + * build included the project or the project is the one + * associated with this builder. + *

                          + *

                          + * If the given project was mentioned in the previous build + * and subsequently deleted, a non- null delta containing the + * deletion will be returned. If the given project was mentioned in the + * previous build and was subsequently created, the returned + * value will be null. + *

                          + *

                          + * A valid delta will be returned only when this method is called during a + * build. The delta returned will be valid only for the duration of the + * enclosing build execution. + *

                          + *

                          + * The delta does not include changes made while this builder is running. + * If {@link #getRule(int, Map)} is overridden to return a scheduling rule other than + * the workspace root, changes performed in other threads during the build + * will not appear in the resource delta. + *

                          + * + * @return the resource delta for the project or null + */ + @Override + public final IResourceDelta getDelta(IProject project) { + return super.getDelta(project); + } + + /** + * Returns the project for which this builder is defined. + * + * @return the project + */ + @Override + public final IProject getProject() { + return super.getProject(); + } + + /** + * Returns the build configuration for which this build was invoked. + * @return the build configuration + * @since 3.7 + */ + @Override + public final IBuildConfiguration getBuildConfig() { + return super.getBuildConfig(); + } + + /** + * Returns whether the given project has already been built during this + * build iteration. + *

                          + * When the entire workspace is being built, the projects are built in + * linear sequence. This method can be used to determine if another project + * precedes this builder's project in that build sequence. If only a single + * project is being built, then there is no build order and this method will + * always return false. + *

                          + * + * @param project the project to check against in the current build order + * @return true if the given project has been built in this + * iteration, and false otherwise. + * @see #needRebuild() + * @since 2.1 + */ + @Override + public final boolean hasBeenBuilt(IProject project) { + return super.hasBeenBuilt(project); + } + + /** + * Returns whether an interrupt request has been made for this build. + * Background autobuild is interrupted when another thread tries to modify + * the workspace concurrently with the build thread. When this occurs, the + * build cycle is flagged as interrupted and the build will be terminated at + * the earliest opportunity. This method allows long running builders to + * respond to this interruption in a timely manner. Builders are not + * required to respond to interruption requests. + *

                          + * + * @return true if the build cycle has been interrupted, and + * false otherwise. + * @since 3.0 + */ + @Override + public final boolean isInterrupted() { + return super.isInterrupted(); + } + + /** + * Indicates that this builder made changes that affect a build configuration that + * precedes this build configuration in the currently executing build order, and thus a + * rebuild will be necessary. + *

                          + * This is an advanced feature that builders should use with caution. This + * can cause workspace builds to iterate until no more builders require + * rebuilds. + *

                          + * + * @see #hasBeenBuilt(IProject) + * @since 2.1 + */ + @Override + public final void needRebuild() { + super.needRebuild(); + } + + /** + * Sets initialization data for this builder. + *

                          + * This method is part of the {@link IExecutableExtension} interface. + *

                          + *

                          + * Subclasses are free to extend this method to pick up initialization + * parameters from the plug-in plug-in manifest (plugin.xml) + * file, but should be sure to invoke this method on their superclass. + *

                          + * For example, the following method looks for a boolean-valued parameter + * named "trace": + * + *

                          +	 * public void setInitializationData(IConfigurationElement cfig, String propertyName, Object data) throws CoreException {
                          +	 * 	super.setInitializationData(cfig, propertyName, data);
                          +	 * 	if (data instanceof Hashtable) {
                          +	 * 		Hashtable args = (Hashtable) data;
                          +	 * 		String traceValue = (String) args.get("trace");
                          +	 * 		TRACING = (traceValue != null && traceValue.equals("true"));
                          +	 * 	}
                          +	 * }
                          +	 * 
                          + *

                          + * @throws CoreException if fails. + */ + @Override + public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException { + //default implementation does nothing + //thwart compiler warning + } + + /** + * Informs this builder that it is being started by the build management + * infrastructure. By the time this method is run, the builder's project is + * available and setInitializationData has been called. The + * default implementation should be called by all overriding methods. + * + * @see #setInitializationData(IConfigurationElement, String, Object) + */ + @Override + protected void startupOnInitialize() { + // reserved for future use + } + + /** + * Returns the scheduling rule that is required for building + * the project build configuration for which this builder is defined. The default + * is the workspace root rule. + *

                          + * The scheduling rule determines which resources in the workspace are + * protected from being modified by other threads while the builder is running. Up until + * Eclipse 3.5, the entire workspace was always locked during a build; + * since Eclipse 3.6, builders can allow resources outside their scheduling + * rule to be modified. + *

                          + * Notes: + *

                            + *
                          • + * If the builder rule is non-null it must be "contained" in the workspace root rule. + * I.e. {@link ISchedulingRule#contains(ISchedulingRule)} must return + * true when invoked on the workspace root with the builder rule. + *
                          • + *
                          • + * The rule returned here may have no effect if the build is invoked within the + * scope of another operation that locks the entire workspace. + *
                          • + *
                          • + * If this method returns any rule other than the workspace root, + * resources outside of the rule scope can be modified concurrently with the build. + * The delta returned by {@link #getDelta(IProject)} for any project + * outside the scope of the builder's rule may not contain changes that occurred + * concurrently with the build. + *
                          + *

                          + *

                          + * Subclasses may override this method. + *

                          + * @noreference This method is not intended to be referenced by clients. + * + * @param kind the kind of build being requested. Valid values include: + *
                            + *
                          • {@link #FULL_BUILD} - indicates a full build.
                          • + *
                          • {@link #INCREMENTAL_BUILD} - indicates an incremental build.
                          • + *
                          • {@link #AUTO_BUILD} - indicates an automatically triggered + * incremental build (autobuilding on).
                          • + *
                          • {@link #CLEAN_BUILD} - indicates a clean request.
                          • + *
                          + * @param args a table of builder-specific arguments keyed by argument name + * (key type: String, value type: String); + * null is equivalent to an empty map. + * @return a scheduling rule which is contained in the workspace root rule + * or null to indicate that no protection against resource + * modification during the build is needed. + * + * @since 3.6 + */ + public ISchedulingRule getRule(int kind, Map args) { + return ResourcesPlugin.getWorkspace().getRoot(); + } + + /** + * Get the context for this invocation of the builder. This is only valid + * in the context of a call to + * {@link #build(int, Map, IProgressMonitor)} + * + *

                          + * This can be used to discover which build configurations are being built before + * and after this build configuration. + *

                          + * + * @return the context for the most recent invocation of the builder + * @since 3.7 + */ + @Override + public final IBuildContext getContext() { + return super.getContext(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java new file mode 100644 index 0000000000..ef5d533eda --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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 Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.internal.preferences.AbstractScope; +import org.eclipse.core.internal.preferences.EclipsePreferences; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IPreferencesService; +import org.osgi.service.prefs.Preferences; + +/** + * Object representing the project scope in the Eclipse preferences + * hierarchy. Can be used as a context for searching for preference + * values (in the org.eclipse.core.runtime.IPreferencesService + * APIs) or for determining the correct preference node to set values in the store. + *

                          + * Project preferences are stored on a per project basis in the + * project's content area as specified by IProject#getLocation. + *

                          + * The path for preferences defined in the project scope hierarchy + * is as follows: /project/<projectName>/<qualifier> + *

                          + *

                          + * This class is not intended to be subclassed. This class may be instantiated. + *

                          + * @see IProject#getLocation() + * @since 3.0 + */ +public final class ProjectScope extends AbstractScope { + /** + * String constant (value of "project") used for the scope name + * for this preference scope. + */ + public static final String SCOPE = "project"; //$NON-NLS-1$ + + private final IProject project; + + /** + * Create and return a new project scope for the given project. The given + * project must not be null. + * + * @param context the project + * @exception IllegalArgumentException if the project is null + */ + public ProjectScope(IProject context) { + super(); + if (context == null) + throw new IllegalArgumentException(); + this.project = context; + } + + @Override + public IEclipsePreferences getNode(String qualifier) { + if (qualifier == null) + throw new IllegalArgumentException(); + IPreferencesService preferencesService = Platform.getPreferencesService(); + Preferences scopeNode = preferencesService.getRootNode().node(SCOPE); + Preferences projectNode = scopeNode.node(project.getName()); + return (IEclipsePreferences) projectNode.node(qualifier); + } + + @Override + public IPath getLocation() { + IPath location = project.getLocation(); + return location == null ? null : location.append(EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME); + } + + @Override + public String getName() { + return SCOPE; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (!(obj instanceof ProjectScope)) + return false; + ProjectScope other = (ProjectScope) obj; + return project.equals(other.project); + } + + @Override + public int hashCode() { + return super.hashCode() * 31 + project.getFullPath().hashCode(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java new file mode 100644 index 0000000000..b0cea77f93 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java @@ -0,0 +1,234 @@ +/******************************************************************************* + * Copyright (c) 2004, 2014 Red Hat Incorporated 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/org/documents/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API + * Red Hat Incorporated - initial implementation + * Martin Oberhuber (Wind River) - [44107] Add symbolic links to ResourceAttributes API + *******************************************************************************/ + +package org.eclipse.core.resources; + +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileSystem; +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.runtime.CoreException; + +/** + * This class represents platform specific attributes of files. + * Any attributes can be added, but only the attributes that are + * supported by the platform will be used. These methods do not set the + * attributes in the file system. + * + * @author Red Hat Incorporated + * @see IResource#getResourceAttributes() + * @see IResource#setResourceAttributes(ResourceAttributes) + * @since 3.1 + * @noextend This class is not intended to be subclassed by clients. + */ +public class ResourceAttributes { + private int attributes; + + /** + * Creates a new resource attributes instance with attributes + * taken from the specified file in the file system. If the specified + * file does not exist or is not accessible, this method has the + * same effect as calling the default constructor. + * + * @param file The file to get attributes from + * @return A resource attributes object + */ + public static ResourceAttributes fromFile(java.io.File file) { + try { + return FileUtil.fileInfoToAttributes(EFS.getStore(file.toURI()).fetchInfo()); + } catch (CoreException e) { + //file could not be accessed + return new ResourceAttributes(); + } + } + + /** + * Creates a new instance of ResourceAttributes. + */ + public ResourceAttributes() { + super(); + } + + /** + * Returns whether this ResourceAttributes object is marked archive. + *

                          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_ARCHIVE}.

                          + * + * @return true if this resource is marked archive, + * false otherwise + * @see #setArchive(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_ARCHIVE + */ + public boolean isArchive() { + return (attributes & EFS.ATTRIBUTE_ARCHIVE) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked executable. + *

                          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_EXECUTABLE}.

                          + * + * @return true if this resource is marked executable, + * false otherwise + * @see #setExecutable(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_EXECUTABLE + */ + public boolean isExecutable() { + return (attributes & EFS.ATTRIBUTE_EXECUTABLE) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked hidden. + *

                          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_HIDDEN}.

                          + * + * @return true if this resource is marked hidden, + * false otherwise + * @see #setHidden(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_HIDDEN + * @since 3.2 + */ + public boolean isHidden() { + return (attributes & EFS.ATTRIBUTE_HIDDEN) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked read only. + *

                          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_READ_ONLY}.

                          + * + * @return true if this resource is marked as read only, + * false otherwise + * @see #setReadOnly(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_READ_ONLY + */ + public boolean isReadOnly() { + return (attributes & EFS.ATTRIBUTE_READ_ONLY) != 0; + } + + /** + * Returns whether this ResourceAttributes object is marked as symbolic link. + *

                          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_SYMLINK}.

                          + * + * @return true if this resource is marked as symbolic link, + * false otherwise + * @see #setSymbolicLink(boolean) + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_SYMLINK + * @since 3.4 + */ + public boolean isSymbolicLink() { + return (attributes & EFS.ATTRIBUTE_SYMLINK) != 0; + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked archive. + *

                          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_ARCHIVE}.

                          + * + * @param archive true to set it to be archive, + * false to unset + * @see #isArchive() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_ARCHIVE + */ + public void setArchive(boolean archive) { + set(EFS.ATTRIBUTE_ARCHIVE, archive); + } + + /** + * Clears all of the bits indicated by the mask. + * @nooverride This method is not intended to be re-implemented or extended by clients. + * @noreference This method is not intended to be referenced by clients. + */ + public void set(int mask, boolean value) { + if (value) + attributes |= mask; + else + attributes &= ~mask; + } + + /** + * Returns whether this ResourceAttributes object has the given mask set. + * @nooverride This method is not intended to be re-implemented or extended by clients. + * @noreference This method is not intended to be referenced by clients. + */ + public boolean isSet(int mask) { + return (attributes & mask) != 0; + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked executable. + *

                          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_EXECUTABLE}.

                          + * + * @param executable true to set it to be executable, + * false to unset + * @see #isExecutable() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_EXECUTABLE + */ + public void setExecutable(boolean executable) { + set(EFS.ATTRIBUTE_EXECUTABLE, executable); + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked hidden + *

                          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_HIDDEN}.

                          + * + * @param hidden true to set it to be marked hidden, + * false to unset + * @see #isHidden() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_HIDDEN + * @since 3.2 + */ + public void setHidden(boolean hidden) { + set(EFS.ATTRIBUTE_HIDDEN, hidden); + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked read only. + *

                          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_READ_ONLY}.

                          + * + * @param readOnly true to set it to be marked read only, + * false to unset + * @see #isReadOnly() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_READ_ONLY + */ + public void setReadOnly(boolean readOnly) { + set(EFS.ATTRIBUTE_READ_ONLY, readOnly); + } + + /** + * Sets or unsets whether this ResourceAttributes object is marked as symbolic link. + *

                          This attribute is used only on file systems supporting {@link EFS#ATTRIBUTE_SYMLINK}.

                          + * + * @param symLink true to set it to be marked as symbolic link, + * false to unset + * @see #isSymbolicLink() + * @see IFileSystem#attributes() + * @see EFS#ATTRIBUTE_SYMLINK + * @since 3.4 + */ + public void setSymbolicLink(boolean symLink) { + set(EFS.ATTRIBUTE_SYMLINK, symLink); + } + + /** + * Returns a string representation of the attributes, suitable + * for debugging purposes only. + */ + @Override + public String toString() { + return "ResourceAttributes(" + attributes + ')'; //$NON-NLS-1$ + } +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java new file mode 100644 index 0000000000..42517a3a3f --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java @@ -0,0 +1,488 @@ +/******************************************************************************* + * 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 PT_FILTER_PROVIDERS + * Serge Beauchamp (Freescale Semiconductor) - [229633] add PT_VARIABLE_PROVIDERS + * James Blackburn (Broadcom Corp.) - ongoing development + * Tom Hochstein (Freescale) - Bug 409996 - 'Restore Defaults' does not work properly on Project Properties > Resource tab + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources; + +import java.util.*; +import org.eclipse.core.internal.preferences.PreferencesService; +import org.eclipse.core.internal.resources.*; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.IJobManager; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.service.debug.DebugOptions; +import org.eclipse.osgi.service.debug.DebugOptionsListener; +import org.osgi.framework.*; + +/** + * The plug-in runtime class for the Resources plug-in. This is + * the starting point for all workspace and resource manipulation. + * A typical sequence of events would be for a dependent plug-in + * to call ResourcesPlugin.getWorkspace(). + * Doing so would cause this plug-in to be activated and the workspace + * (if any) to be loaded from disk and initialized. + * + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class ResourcesPlugin extends Plugin { + /** + * Unique identifier constant (value "org.eclipse.core.resources") + * for the standard Resources plug-in. + */ + public static final String PI_RESOURCES = "org.eclipse.core.resources"; //$NON-NLS-1$ + + /*==================================================================== + * Constants defining the ids of the standard workspace extension points: + *====================================================================*/ + + /** + * Simple identifier constant (value "builders") + * for the builders extension point. + */ + public static final String PT_BUILDERS = "builders"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "natures") + * for the natures extension point. + */ + public static final String PT_NATURES = "natures"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "markers") + * for the markers extension point. + */ + public static final String PT_MARKERS = "markers"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "fileModificationValidator") + * for the file modification validator extension point. + */ + public static final String PT_FILE_MODIFICATION_VALIDATOR = "fileModificationValidator"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "moveDeleteHook") + * for the move/delete hook extension point. + * + * @since 2.0 + */ + public static final String PT_MOVE_DELETE_HOOK = "moveDeleteHook"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "teamHook") + * for the team hook extension point. + * + * @since 2.1 + */ + public static final String PT_TEAM_HOOK = "teamHook"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "refreshProviders") + * for the auto-refresh refresh providers extension point. + * + * @since 3.0 + */ + public static final String PT_REFRESH_PROVIDERS = "refreshProviders"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "modelProviders") + * for the model providers extension point. + * + * @since 3.2 + */ + public static final String PT_MODEL_PROVIDERS = "modelProviders"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "variableProviders") + * for the variable providers extension point. + * + * @since 3.6 + */ + public static final String PT_VARIABLE_PROVIDERS = "variableResolvers"; //$NON-NLS-1$ + + /** + * Simple identifier constant (value "filterMatchers") + * for the filter matchers extension point. + * + * @since 3.6 + */ + public static final String PT_FILTER_MATCHERS = "filterMatchers"; //$NON-NLS-1$ + + /** + * Constant identifying the job family identifier for the background autobuild job. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @since 3.0 + */ + public static final Object FAMILY_AUTO_BUILD = new Object(); + + /** + * Constant identifying the job family identifier for the background auto-refresh job. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @since 3.1 + */ + public static final Object FAMILY_AUTO_REFRESH = new Object(); + + /** + * Constant identifying the job family identifier for a background build job. All clients + * that schedule background jobs for performing builds should include this job + * family in their implementation of belongsTo. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @see Job#belongsTo(Object) + * @since 3.0 + */ + public static final Object FAMILY_MANUAL_BUILD = new Object(); + + /** + * Constant identifying the job family identifier for a background refresh job. All clients + * that schedule background jobs for performing refreshing should include this job + * family in their implementation of belongsTo. + * + * @see IJobManager#join(Object, IProgressMonitor) + * @see Job#belongsTo(Object) + * @since 3.4 + */ + public static final Object FAMILY_MANUAL_REFRESH = new Object(); + + /** + * Name of a preference indicating the encoding to use when reading text + * files in the workspace. The value is a string, and may + * be the default empty string, indicating that the file system encoding should + * be used instead. The file system encoding can be retrieved using + * System.getProperty("file.encoding"). + * There is also a convenience method getEncoding which returns + * the value of this preference, or the file system encoding if this + * preference is not set. + *

                          + * Note that there is no guarantee that the value is a supported encoding. + * Callers should be prepared to handle UnsupportedEncodingException + * where this encoding is used. + *

                          + * + * @see #getEncoding() + * @see java.io.UnsupportedEncodingException + */ + public static final String PREF_ENCODING = "encoding"; //$NON-NLS-1$ + + /** + * Common prefix for workspace preference names. + * @since 2.1 + */ + private static final String PREF_DESCRIPTION_PREFIX = "description."; //$NON-NLS-1$ + + /** + * @deprecated Do not use. + * @since 3.0 + */ + @Deprecated + public static final String PREF_MAX_NOTIFICATION_DELAY = "maxnotifydelay"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether the workspace performs auto- + * builds. + * + * @see IWorkspaceDescription#isAutoBuilding() + * @see IWorkspaceDescription#setAutoBuilding(boolean) + * @since 2.1 + */ + public static final String PREF_AUTO_BUILDING = PREF_DESCRIPTION_PREFIX + "autobuilding"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the order projects in the workspace + * are built. + * + * @see IWorkspaceDescription#getBuildOrder() + * @see IWorkspaceDescription#setBuildOrder(String[]) + * @since 2.1 + */ + public static final String PREF_BUILD_ORDER = PREF_DESCRIPTION_PREFIX + "buildorder"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether to use the workspace's + * default order for building projects. + * @since 2.1 + */ + public static final String PREF_DEFAULT_BUILD_ORDER = PREF_DESCRIPTION_PREFIX + "defaultbuildorder"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum number of times that the + * workspace should rebuild when builders affect projects that have already + * been built. + * + * @see IWorkspaceDescription#getMaxBuildIterations() + * @see IWorkspaceDescription#setMaxBuildIterations(int) + * @since 2.1 + */ + public static final String PREF_MAX_BUILD_ITERATIONS = PREF_DESCRIPTION_PREFIX + "maxbuilditerations"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether to apply the specified history size policy. + * + * @see IWorkspaceDescription#isApplyFileStatePolicy() + * @see IWorkspaceDescription#setApplyFileStatePolicy(boolean) + * @since 3.6 + */ + public static final String PREF_APPLY_FILE_STATE_POLICY = PREF_DESCRIPTION_PREFIX + "applyfilestatepolicy"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum number of milliseconds a + * file state should be kept in the local history + * + * @see IWorkspaceDescription#getFileStateLongevity() + * @see IWorkspaceDescription#setFileStateLongevity(long) + * @since 2.1 + */ + public static final String PREF_FILE_STATE_LONGEVITY = PREF_DESCRIPTION_PREFIX + "filestatelongevity"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum permitted size of a file + * to be stored in the local history + * + * @see IWorkspaceDescription#getMaxFileStateSize() + * @see IWorkspaceDescription#setMaxFileStateSize(long) + * @since 2.1 + */ + public static final String PREF_MAX_FILE_STATE_SIZE = PREF_DESCRIPTION_PREFIX + "maxfilestatesize"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring the maximum number of states per + * file that can be stored in the local history. + * + * @see IWorkspaceDescription#getMaxFileStates() + * @see IWorkspaceDescription#setMaxFileStates(int) + * @since 2.1 + */ + public static final String PREF_MAX_FILE_STATES = PREF_DESCRIPTION_PREFIX + "maxfilestates"; //$NON-NLS-1$ + /** + * Name of a preference for configuring the amount of time in milliseconds + * between automatic workspace snapshots + * + * @see IWorkspaceDescription#getSnapshotInterval() + * @see IWorkspaceDescription#setSnapshotInterval(long) + * @since 2.1 + */ + public static final String PREF_SNAPSHOT_INTERVAL = PREF_DESCRIPTION_PREFIX + "snapshotinterval"; //$NON-NLS-1$ + + /** + * Name of a preference for turning off support for linked resources. When + * this preference is set to "true", attempting to create linked resources will fail. + * @since 2.1 + */ + public static final String PREF_DISABLE_LINKING = PREF_DESCRIPTION_PREFIX + "disableLinking";//$NON-NLS-1$ + + /** + * Name of a preference for configuring whether the workspace performs auto- + * refresh. Auto-refresh installs a file-system listener, or performs + * periodic file-system polling to actively discover changes in the resource + * hierarchy. + * @since 3.0 + */ + public static final String PREF_AUTO_REFRESH = "refresh.enabled"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether out-of-sync resources are automatically + * asynchronously refreshed, when discovered to be out-of-sync by the workspace. + *

                          + * This preference suppresses out-of-sync CoreException for some read methods, including: + * {@link IFile#getContents()} & {@link IFile#getContentDescription()}. + *

                          + *

                          + * In the future the workspace may enable other lightweight auto-refresh mechanisms when this + * preference is true. (The existing {@link ResourcesPlugin#PREF_AUTO_REFRESH} will continue + * to enable filesystem hooks and the existing polling based monitor.) + *

                          + * See the discussion: https://bugs.eclipse.org/303517 + * @since 3.7 + */ + public static final String PREF_LIGHTWEIGHT_AUTO_REFRESH = "refresh.lightweight.enabled"; //$NON-NLS-1$ + + /** + * Name of a preference for configuring whether encodings for derived + * resources within the project should be stored in a separate derived + * preference file. + * + * @since 3.7 + */ + public static final String PREF_SEPARATE_DERIVED_ENCODINGS = "separateDerivedEncodings"; //$NON-NLS-1$ + + /** + * Default setting for {@value #PREF_SEPARATE_DERIVED_ENCODINGS}. + * + * @since 3.9 + */ + public static final boolean DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS = false; + + /** + * The single instance of this plug-in runtime class. + */ + private static ResourcesPlugin plugin; + + /** + * The workspace managed by the single instance of this + * plug-in runtime class, or null is there is none. + */ + private static Workspace workspace = null; + + private ServiceRegistration workspaceRegistration; + private ServiceRegistration debugRegistration; + + /** + * Constructs an instance of this plug-in runtime class. + *

                          + * An instance of this plug-in runtime class is automatically created + * when the facilities provided by the Resources plug-in are required. + * Clients must never explicitly instantiate a plug-in runtime class. + *

                          + */ + public ResourcesPlugin() { + plugin = this; + } + + /** + * Constructs a brand new workspace structure at the location in the local file system + * identified by the given path and returns a new workspace object. + * + * @exception CoreException if the workspace structure could not be constructed. + * Reasons include: + *
                            + *
                          • There is an existing workspace structure on at the given location + * in the local file system. + *
                          • A file exists at the given location in the local file system. + *
                          • A directory could not be created at the given location in the + * local file system. + *
                          + */ + private static void constructWorkspace() throws CoreException { + new LocalMetaArea().createMetaArea(); + } + + /** + * Returns the encoding to use when reading text files in the workspace. + * This is the value of the PREF_ENCODING preference, or the + * file system encoding (System.getProperty("file.encoding")) + * if the preference is not set. + *

                          + * Note that this method does not check whether the result is a supported + * encoding. Callers should be prepared to handle + * UnsupportedEncodingException where this encoding is used. + * + * @return the encoding to use when reading text files in the workspace + * @see java.io.UnsupportedEncodingException + */ + public static String getEncoding() { + String enc = getPlugin().getPluginPreferences().getString(PREF_ENCODING); + if (enc == null || enc.length() == 0) { + enc = System.getProperty("file.encoding"); //$NON-NLS-1$ + } + return enc; + } + + /** + * Returns the Resources plug-in. + * + * @return the single instance of this plug-in runtime class + */ + public static ResourcesPlugin getPlugin() { + return plugin; + } + + /** + * Returns the workspace. The workspace is not accessible after the resources + * plug-in has shutdown. + * + * @return the workspace that was created by the single instance of this + * plug-in class. + */ + public static IWorkspace getWorkspace() { + if (workspace == null) + throw new IllegalStateException(Messages.resources_workspaceClosed); + return workspace; + } + + /** + * This implementation of the corresponding {@link BundleActivator} method + * closes the workspace without saving. + * @see BundleActivator#stop(BundleContext) + */ + @Override + public void stop(BundleContext context) throws Exception { + super.stop(context); + + // unregister debug options listener + debugRegistration.unregister(); + debugRegistration = null; + + if (workspace == null) { + return; + } + if (workspaceRegistration != null) { + workspaceRegistration.unregister(); + } + // save the preferences for this plug-in + getPlugin().savePluginPreferences(); + workspace.close(null); + + // Forget workspace only if successfully closed, to + // make it easier to debug cases where close() is failing. + workspace = null; + workspaceRegistration = null; + } + + /** + * This implementation of the corresponding {@link BundleActivator} method + * opens the workspace. + * @see BundleActivator#start(BundleContext) + */ + @Override + public void start(BundleContext context) throws Exception { + System.err.println("RESOURCES: BUG 519776"); + super.start(context); + + // register debug options listener + Hashtable properties = new Hashtable<>(2); + properties.put(DebugOptions.LISTENER_SYMBOLICNAME, PI_RESOURCES); + debugRegistration = context.registerService(DebugOptionsListener.class, Policy.RESOURCES_DEBUG_OPTIONS_LISTENER, properties); + + if (!new LocalMetaArea().hasSavedWorkspace()) { + constructWorkspace(); + } + // Remember workspace before opening, to + // make it easier to debug cases where open() is failing. + workspace = new Workspace(); + PlatformURLResourceConnection.startup(workspace.getRoot().getLocation()); + initializePreferenceLookupOrder(); + IStatus result = workspace.open(null); + if (!result.isOK()) + getLog().log(result); + workspaceRegistration = context.registerService(IWorkspace.class, workspace, null); + } + + /* + * Add the project scope to the preference service's default look-up order so + * people get it for free + */ + private void initializePreferenceLookupOrder() { + PreferencesService service = PreferencesService.getDefault(); + String[] original = service.getDefaultDefaultLookupOrder(); + List newOrder = new ArrayList<>(); + // put the project scope first on the list + newOrder.add(ProjectScope.SCOPE); + for (String entry : original) + newOrder.add(entry); + service.setDefaultDefaultLookupOrder(newOrder.toArray(new String[newOrder.size()])); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java new file mode 100644 index 0000000000..039c28c807 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.resources; + +import org.eclipse.core.internal.resources.InternalWorkspaceJob; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * A job that makes an atomic modification to the workspace. Clients must + * implement the abstract method runInWorkspace instead + * of the usual Job.run method. + *

                          + * After running a method that modifies resources in the workspace, + * registered listeners receive after-the-fact notification of + * what just transpired, in the form of a resource change event. + * This method allows clients to call a number of + * methods that modify resources and only have resource + * change event notifications reported at the end of the entire + * batch. This mechanism is used to avoid unnecessary builds + * and notifications. + *

                          + *

                          + * Platform may decide to perform notifications during the operation. + * The reason for this is that it is possible for multiple threads + * to be modifying the workspace concurrently. When one thread finishes modifying + * the workspace, a notification is required to prevent responsiveness problems, + * even if the other operation has not yet completed. + *

                          + *

                          + * A WorkspaceJob is the asynchronous equivalent of ICoreRunnable + *

                          + *

                          + * Note that the workspace is not locked against other threads during the execution + * of a workspace job. Other threads can be modifying the workspace concurrently + * with a workspace job. To obtain exclusive access to a portion of the workspace, + * set the scheduling rule on the job to be a resource scheduling rule. The + * interface IResourceRuleFactory is used to create a scheduling rule + * for a particular workspace modification operation. + *

                          + * @see ICoreRunnable + * @see org.eclipse.core.resources.IResourceRuleFactory + * @see IWorkspace#run(ICoreRunnable, ISchedulingRule, int, IProgressMonitor) + * @since 3.0 + */ +public abstract class WorkspaceJob extends InternalWorkspaceJob { + /** + * Creates a new workspace 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 WorkspaceJob(String name) { + super(name); + } + + /** + * Runs the operation, reporting progress to and accepting + * cancellation requests from the given progress monitor. + *

                          + * Implementors of this method should check the progress monitor + * for cancellation when it is safe and appropriate to do so. The cancellation + * request should be propagated to the caller by throwing + * OperationCanceledException. + *

                          + * + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @return the result of running the operation + * @exception CoreException if this operation fails. + */ + @Override + public abstract IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java new file mode 100644 index 0000000000..cafa50fa25 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/AbstractFileInfoMatcher.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 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 + *******************************************************************************/ +package org.eclipse.core.resources.filtermatchers; + +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; + +/** + * The abstract base class for all file info matchers. Instances + * of this class are provided using the org.eclipse.core.resources.filterMatchers + * extension point. + * + * @since 3.6 + */ +public abstract class AbstractFileInfoMatcher { + + /** + * Tests the given {@link FileInfo} + * + * @param parent the parent container + * @param fileInfo the {@link FileInfo} object to test + * @return true if the given {@link FileInfo} matches, + * and false otherwise. + * @throws CoreException the implementor should throw a CoreException if, + * in the case that the parent or fileInfo doesn't exist in the workspace + * or in the file system, the return value can't be determined. + */ + public abstract boolean matches(IContainer parent, IFileInfo fileInfo) throws CoreException; + + /** + * Sets initialization data for this matcher. + * @param project + * @param arguments + * @throws CoreException if initialization failed + */ + public abstract void initialize(IProject project, Object arguments) throws CoreException; +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java new file mode 100644 index 0000000000..bf37be3b26 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/CompoundFileInfoMatcher.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2008, 2014 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) - [252996] initial API and implementation + * IBM Corporation - ongoing implementation + *******************************************************************************/ +package org.eclipse.core.resources.filtermatchers; + +import org.eclipse.core.internal.resources.FilterDescriptor; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; + +/** + * Resource Filter Type allowing serializing sub filters as the arguments + * @since 3.6 + */ +public abstract class CompoundFileInfoMatcher extends AbstractFileInfoMatcher { + + protected AbstractFileInfoMatcher[] matchers; + + private AbstractFileInfoMatcher instantiate(IProject project, FileInfoMatcherDescription filter) throws CoreException { + IFilterMatcherDescriptor desc = project.getWorkspace().getFilterMatcherDescriptor(filter.getId()); + if (desc != null) { + AbstractFileInfoMatcher matcher = ((FilterDescriptor) desc).createFilter(); + matcher.initialize(project, filter.getArguments()); + return matcher; + } + return null; + } + + @Override + public final void initialize(IProject project, Object arguments) throws CoreException { + FileInfoMatcherDescription[] filters = (FileInfoMatcherDescription[]) arguments; + matchers = new AbstractFileInfoMatcher[filters != null ? filters.length : 0]; + for (int i = 0; i < matchers.length; i++) + matchers[i] = instantiate(project, filters[i]); + } +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html new file mode 100644 index 0000000000..7f43f8e202 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/filtermatchers/package.html @@ -0,0 +1,21 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the resource filter matchers. + +

                          Package Specification

                          +

                          +This package specifies the APIs in the Resources plug-in that are intended +to be implemented and used by plug-ins using the filterMatchers extension point. +

                          +@since 3.6 +

                          + + + diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java new file mode 100644 index 0000000000..98c3dd165c --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.*; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.*; + +/** + * A resource mapping that obtains the traversals for its model object + * from a set of child mappings. + *

                          + * This class is not intended to be subclasses by clients. + * + * @since 3.2 + */ +public final class CompositeResourceMapping extends ResourceMapping { + private final ResourceMapping[] mappings; + private final Object modelObject; + private IProject[] projects; + private String providerId; + + /** + * Create a composite mapping that obtains its traversals from a set of sub-mappings. + * @param modelObject the model object for this mapping + * @param mappings the sub-mappings from which the traversals are obtained + */ + public CompositeResourceMapping(String providerId, Object modelObject, ResourceMapping[] mappings) { + this.modelObject = modelObject; + this.mappings = mappings; + this.providerId = providerId; + } + + @Override + public boolean contains(ResourceMapping mapping) { + for (int i = 0; i < mappings.length; i++) { + ResourceMapping childMapping = mappings[i]; + if (childMapping.contains(mapping)) { + return true; + } + } + return false; + } + + /** + * Return the resource mappings contained in this composite. + * @return Return the resource mappings contained in this composite. + */ + public ResourceMapping[] getMappings() { + return mappings; + } + + @Override + public Object getModelObject() { + return modelObject; + } + + @Override + public String getModelProviderId() { + return providerId; + } + + @Override + public IProject[] getProjects() { + if (projects == null) { + Set result = new HashSet<>(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + result.addAll(Arrays.asList(mapping.getProjects())); + } + projects = result.toArray(new IProject[result.size()]); + } + return projects; + } + + @Override + public ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + SubMonitor subMonitor = SubMonitor.convert(monitor, mappings.length); + List result = new ArrayList<>(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + Collections.addAll(result, mapping.getTraversals(context, subMonitor.newChild(1))); + } + return result.toArray(new ResourceTraversal[result.size()]); + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java new file mode 100644 index 0000000000..669603bbee --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 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.mapping; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A model provider descriptor contains information about a model provider + * obtained from the plug-in manifest (plugin.xml) file. + *

                          + * Model provider descriptors are platform-defined objects that exist + * independent of whether that model provider's plug-in has been started. + * In contrast, a model provider's runtime object (ModelProvider) + * generally runs plug-in-defined code. + *

                          + * + * @see org.eclipse.core.resources.mapping.ModelProvider + * @since 3.2 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IModelProviderDescriptor { + + /** + * Return the ids of model providers that this model provider extends. + * @return the ids of model providers that this model provider extends + */ + public String[] getExtendedModels(); + + /** + * Returns the unique identifier of this model provider. + *

                          + * The model provider identifier is composed of the model provider's + * plug-in id and the simple id of the provider extension. For example, if + * plug-in "com.xyz" defines a provider extension with id + * "myModelProvider", the unique model provider identifier will be + * "com.xyz.myModelProvider". + *

                          + * + * @return the unique model provider identifier + */ + public String getId(); + + /** + * Returns a displayable label for this model provider. + * Returns the empty string if no label for this provider + * is specified in the plug-in manifest file. + *

                          Note that any translation specified in the plug-in manifest + * file is automatically applied. + *

                          + * + * @return a displayable string label for this model provider, + * possibly the empty string + */ + public String getLabel(); + + /** + * From the provides set of resources, return those that match the enablement + * rule specified for the model provider descriptor. The resource mappings + * for the returned resources can then be obtained by invoking + * {@link ModelProvider#getMappings(IResource[], ResourceMappingContext, IProgressMonitor)} + * + * @param resources the resources + * @return the resources that match the descriptor's enablement rule + */ + public IResource[] getMatchingResources(IResource[] resources) throws CoreException; + + /** + * Return the set of traversals that overlap with the resources that + * this descriptor matches. + * + * @param traversals the traversals being tested + * @return the subset of these traversals that overlap with the resources + * that match this descriptor + * @throws CoreException + */ + public ResourceTraversal[] getMatchingTraversals(ResourceTraversal[] traversals) throws CoreException; + + /** + * Return the model provider for this descriptor, instantiating it if it is + * the first time the method is called. + * + * @return the model provider for this descriptor + * @exception CoreException if the model provider could not be instantiated for + * some reason + */ + public ModelProvider getModelProvider() throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java new file mode 100644 index 0000000000..f28fdca41b --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 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.mapping; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * This factory is used to build a resource delta that represents a proposed change + * that can then be passed to the {@link ResourceChangeValidator#validateChange(IResourceDelta, IProgressMonitor)} + * method in order to validate the change with any model providers stored in those resources. + * The deltas created by calls to the methods of this interface will be the same as + * those generated by the workspace if the proposed operations were performed. + *

                          + * This factory does not validate that the proposed operation is valid given the current + * state of the resources and any other proposed changes. It only records the + * delta that would result. + * + * @see ResourceChangeValidator + * @see ModelProvider + * @since 3.2 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IResourceChangeDescriptionFactory { + + /** + * Record a delta that represents a content change for the given file. + * @param file the file whose contents will be changed + */ + public void change(IFile file); + + /** + * Record the set of deltas representing the closed of a project. + * @param project the project that will be closed + */ + public void close(IProject project); + + /** + * Record the set of deltas representing a copy of the given resource to the + * given workspace path. + * @param resource the resource that will be copied + * @param destination the full workspace path of the destination the resource is being copied to + */ + public void copy(IResource resource, IPath destination); + + /** + * Record a delta that represents a resource being created. + * @param resource the resource that is created + */ + public void create(IResource resource); + + /** + * Record the set of deltas representing a deletion of the given resource. + * @param resource the resource that will be deleted + */ + public void delete(IResource resource); + + /** + * Return the proposed delta that has been accumulated by this factory. + * @return the proposed delta that has been accumulated by this factory + */ + public IResourceDelta getDelta(); + + /** + * Record the set of deltas representing a move of the given resource to the + * given workspace path. Note that this API is used to describe a resource + * being moved to another path in the workspace, rather than a move in the + * file system. + * @param resource the resource that will be moved + * @param destination the full workspace path of the destination the resource is being moved to + */ + public void move(IResource resource, IPath destination); + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java new file mode 100644 index 0000000000..48b97c5c91 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - ongoing development + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.*; +import org.eclipse.core.internal.resources.mapping.ModelProviderManager; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * Represents the provider of a logical model. The main purpose of this + * API is to support batch operations on sets of ResourceMapping + * objects that are part of the same model. + * + *

                          + * This class may be subclassed by clients. + *

                          + * @see org.eclipse.core.resources.mapping.ResourceMapping + * @since 3.2 + */ +public abstract class ModelProvider extends PlatformObject { + /** + * The model provider id of the Resources model. + */ + public static final String RESOURCE_MODEL_PROVIDER_ID = "org.eclipse.core.resources.modelProvider"; //$NON-NLS-1$ + + private IModelProviderDescriptor descriptor; + + /** + * Returns the descriptor for the model provider of the given id + * or null if the provider has not been registered. + * @param id a model provider id. + * @return the descriptor for the model provider of the given id + * or null if the provider has not been registered + */ + public static IModelProviderDescriptor getModelProviderDescriptor(String id) { + IModelProviderDescriptor[] descs = ModelProviderManager.getDefault().getDescriptors(); + for (int i = 0; i < descs.length; i++) { + IModelProviderDescriptor descriptor = descs[i]; + if (descriptor.getId().equals(id)) { + return descriptor; + } + } + return null; + } + + /** + * Returns the descriptors for all model providers that are registered. + * + * @return the descriptors for all model providers that are registered. + */ + public static IModelProviderDescriptor[] getModelProviderDescriptors() { + return ModelProviderManager.getDefault().getDescriptors(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ModelProvider) { + ModelProvider other = (ModelProvider) obj; + return other.getDescriptor().getId().equals(getDescriptor().getId()); + } + return super.equals(obj); + } + + /** + * Returns the descriptor of this model provider. The descriptor + * is set during initialization so implements cannot call this method + * until after the initialize method is invoked. + * @return the descriptor of this model provider + */ + public final IModelProviderDescriptor getDescriptor() { + return descriptor; + } + + /** + * Returns the unique identifier of this model provider. + *

                          + * The model provider identifier is composed of the model provider's + * plug-in id and the simple id of the provider extension. For example, if + * plug-in "com.xyz" defines a provider extension with id + * "myModelProvider", the unique model provider identifier will be + * "com.xyz.myModelProvider". + *

                          + * + * @return the unique model provider identifier + */ + public final String getId() { + return descriptor.getId(); + } + + /** + * Returns the resource mappings that cover the given resource. + * By default, an empty array is returned. Subclass may override + * this method but should consider overriding either + * {@link #getMappings(IResource[], ResourceMappingContext, IProgressMonitor)} + * or {@link #getMappings(ResourceTraversal[], ResourceMappingContext, IProgressMonitor)} + * if more context is needed to determine the proper mappings. + * + * @param resource the resource + * @param context a resource mapping context + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the resource mappings that cover the given resource. + * @exception CoreException + */ + public ResourceMapping[] getMappings(IResource resource, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + return new ResourceMapping[0]; + } + + /** + * Returns the set of mappings that cover the given resources. + * This method is used to map operations on resources to + * operations on resource mappings. By default, this method + * calls getMapping(IResource) for each resource. + *

                          + * Subclasses may override this method. + *

                          + * + * @param resources the resources + * @param context a resource mapping context + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the set of mappings that cover the given resources + * @exception CoreException + */ + public ResourceMapping[] getMappings(IResource[] resources, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + Set mappings = new HashSet<>(); + for (int i = 0; i < resources.length; i++) { + IResource resource = resources[i]; + ResourceMapping[] resourceMappings = getMappings(resource, context, monitor); + if (resourceMappings.length > 0) + mappings.addAll(Arrays.asList(resourceMappings)); + } + return mappings.toArray(new ResourceMapping[mappings.size()]); + } + + /** + * Returns the set of mappings that overlap with the given resource traversals. + * This method is used to map operations on resources to + * operations on resource mappings. By default, this method + * calls {@link #getMappings(IResource[], ResourceMappingContext, IProgressMonitor)} + * with the resources extracted from each traversal. + *

                          + * Subclasses may override this method. + *

                          + * + * @param traversals the traversals + * @param context a resource mapping context + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the set of mappings that overlap with the given resource traversals + */ + public ResourceMapping[] getMappings(ResourceTraversal[] traversals, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + Set result = new HashSet<>(); + for (int i = 0; i < traversals.length; i++) { + ResourceTraversal traversal = traversals[i]; + ResourceMapping[] mappings = getMappings(traversal.getResources(), context, monitor); + for (int j = 0; j < mappings.length; j++) + result.add(mappings[j]); + } + return result.toArray(new ResourceMapping[result.size()]); + } + + /** + * Returns a set of traversals that cover the given resource mappings. The + * provided mappings must be from this provider or one of the providers this + * provider extends. + *

                          + * The default implementation accumulates the traversals from the given + * mappings. Subclasses can override to provide a more optimal + * transformation. + *

                          + * + * @param mappings the mappings being mapped to resources + * @param context the context used to determine the set of traversals that + * cover the mappings + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a set of traversals that cover the given mappings + * @exception CoreException + */ + public ResourceTraversal[] getTraversals(ResourceMapping[] mappings, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { + SubMonitor subMonitor = SubMonitor.convert(monitor, mappings.length); + List traversals = new ArrayList<>(); + for (int i = 0; i < mappings.length; i++) { + ResourceMapping mapping = mappings[i]; + Collections.addAll(traversals, mapping.getTraversals(context, subMonitor.newChild(1))); + } + return traversals.toArray(new ResourceTraversal[traversals.size()]); + } + + @Override + public int hashCode() { + return getDescriptor().getId().hashCode(); + } + + /** + * This method is called by the model provider framework when the model + * provider is instantiated. This method should not be called by clients and + * cannot be overridden by subclasses. However, it invokes the + * initialize method once the descriptor is set so subclasses + * can override that method if they need to do additional initialization. + * + * @param desc the description of the provider as it appears in the plugin manifest + * @noreference This method is not intended to be referenced by clients. + */ + public final void init(IModelProviderDescriptor desc) { + if (descriptor != null) { + // prevent subsequent calls from damaging this instance + return; + } + descriptor = desc; + initialize(); + } + + /** + * Initialization method that is called after the descriptor + * of this provider is set. Subclasses may override. + */ + protected void initialize() { + // Do nothing + } + + /** + * Validates the proposed changes contained in the given delta. + *

                          + * This method must return either a {@link ModelStatus}, or a {@link MultiStatus} + * whose children are {@link ModelStatus}. The severity of the returned status + * indicates the severity of the possible side-effects of the operation. Any + * severity other than OK will be shown to the user. The + * message should be a human readable message that will allow the user to + * make a decision on whether to continue with the operation. The model + * provider id should indicate which model is flagging the possible side effects. + *

                          + * This default implementation accepts all changes and returns a status with + * severity OK. Subclasses should override to perform + * validation specific to their model. + *

                          + * + * @param delta a delta tree containing the proposed changes + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a status indicating any potential side effects + * on the model that provided this validator. + */ + public IStatus validateChange(IResourceDelta delta, IProgressMonitor monitor) { + return new ModelStatus(IStatus.OK, ResourcesPlugin.PI_RESOURCES, descriptor.getId(), Status.OK_STATUS.getMessage()); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java new file mode 100644 index 0000000000..95fe5abc7f --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 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.resources.mapping; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.Status; + +/** + * A status returned by a model from the resource operation validator. + * The severity indicates the severity of the possible side effects + * of the operation. Any severity other than OK should be + * shown to the user. The message should be a human readable message that + * will allow the user to make a decision as to whether to continue with the + * operation. The model provider id should indicate which model is flagging the + * the possible side effects. + *

                          + * Clients may instantiate or subclass this class. + *

                          + * + * @since 3.2 + */ +public class ModelStatus extends Status { + + private final String modelProviderId; + + /** + * Create a model status. + * + * @param severity the severity + * @param pluginId the plugin id + * @param modelProviderId the model provider id + * @param message the message + */ + public ModelStatus(int severity, String pluginId, String modelProviderId, String message) { + super(severity, pluginId, 0, message, null); + Assert.isNotNull(modelProviderId); + this.modelProviderId = modelProviderId; + } + + /** + * Return the id of the model provider from which this status originated. + * + * @return the id of the model provider from which this status originated + */ + public String getModelProviderId() { + return modelProviderId; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java new file mode 100644 index 0000000000..227b8508ce --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java @@ -0,0 +1,321 @@ +/******************************************************************************* + * 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.resources.mapping; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A remote mapping context provides a model element with a view of the remote + * state of local resources as they relate to a repository operation that is in + * progress. A repository provider can pass an instance of this interface to a + * model element when obtaining a set of traversals for a model element. This + * allows the model element to query the remote state of a resource in order to + * determine if there are resources that exist remotely but do not exist locally + * that should be included in the traversal. + *

                          + * This class may be subclassed by clients. + *

                          + * + * @see ResourceMapping + * @see ResourceMappingContext + * @since 3.2 + */ +public abstract class RemoteResourceMappingContext extends ResourceMappingContext { + + /** + * Refresh flag constant (bit mask value 1) indicating that the mapping will + * be making use of the contents of the files covered by the traversals + * being refreshed. + */ + public static final int FILE_CONTENTS_REQUIRED = 1; + + /** + * Refresh flag constant (bit mask value 0) indicating that no additional + * refresh behavior is required. + */ + public static final int NONE = 0; + + /** + * For three-way comparisons, returns an instance of IStorage in order to + * allow the caller to access the contents of the base resource that + * corresponds to the given local resource. The base of a resource is the + * contents of the resource before any local modifications were made. If the + * base file does not exist, or if this is a two-way comparison, null + * is returned. The provided local file handle need not exist locally. A exception + * is thrown if the corresponding base resource is not a file. + *

                          + * This method may be long running as a server may need to be contacted to + * obtain the contents of the file. + *

                          + * + * @param file the local file + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a storage that provides access to the contents of the local + * resource's corresponding remote resource. If the remote file does not + * exist, null is returned + * @exception CoreException if the contents could not be fetched. Reasons + * include: + *
                            + *
                          • The server could not be contacted for some reason. + *
                          • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                          • + *
                          + */ + public abstract IStorage fetchBaseContents(IFile file, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the members of the base resource corresponding to the given container. + * The container and the returned members need not exist locally and may not + * include all children that exist locally. An empty list is returned if the base resource + * is empty or does not exist. An exception is thrown if the base resource is not + * capable of having members. This method returns null if + * the base members cannot be computed, in which case clients should call + * {@link #fetchMembers(IContainer, IProgressMonitor)} which returns the + * combined members for the base and remote. + *

                          + *

                          + * This method may be long running as a server may need to be contacted to + * obtain the members of the base resource. + *

                          + *

                          + * This default implementation always returns null, but subclasses + * may override. + *

                          + * @param container the local container + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the members of the base resource corresponding to the given container + * @exception CoreException if the members could not be fetched. Reasons + * include: + *
                            + *
                          • The server could not be contacted for some reason.
                          • + *
                          • The base resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                          • + *
                          + * @since 3.3 + */ + public IResource[] fetchBaseMembers(IContainer container, IProgressMonitor monitor) throws CoreException { + //default implementation does nothing + //thwart compiler warning + return null; + } + + /** + * Returns the combined members of the base and remote resources corresponding + * to the given container. The container need not exist locally and the result may + * include entries that do not exist locally and may not include all local + * children. An empty list is returned if the remote resource which + * corresponds to the container is empty or if the remote does not exist. An + * exception is thrown if the corresponding remote is not capable of having + * members. + *

                          + * This method may be long running as a server may need to be contacted to + * obtain the members of the container's corresponding remote resource. + *

                          + * + * @param container the local container + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return returns the combined members of the base and remote resources + * corresponding to the given container. + * @exception CoreException if the members could not be fetched. Reasons + * include: + *
                            + *
                          • The server could not be contacted for some reason.
                          • + *
                          • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                          • + *
                          + */ + public abstract IResource[] fetchMembers(IContainer container, IProgressMonitor monitor) throws CoreException; + + /** + * Returns an instance of IStorage in order to allow the caller to access + * the contents of the remote that corresponds to the given local resource. + * If the remote file does not exist, null is returned. The + * provided local file handle need not exist locally. A exception is thrown + * if the corresponding remote resource is not a file. + *

                          + * This method may be long running as a server may need to be contacted to + * obtain the contents of the file. + *

                          + * + * @param file the local file + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a storage that provides access to the contents of the local + * resource's corresponding remote resource. If the remote file does not + * exist, null is returned + * @exception CoreException if the contents could not be fetched. Reasons + * include: + *
                            + *
                          • The server could not be contacted for some reason.
                          • + *
                          • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                          • + *
                          + */ + public abstract IStorage fetchRemoteContents(IFile file, IProgressMonitor monitor) throws CoreException; + + /** + * Returns the members of the remote resource corresponding to the given container. + * The container and the returned members need not exist locally and may not + * include all children that exist locally. An empty list is returned if the remote resource + * is empty or does not exist. An exception is thrown if the remote resource is not + * capable of having members. This method returns null if + * the remote members cannot be computed, in which case clients should call + * {@link #fetchMembers(IContainer, IProgressMonitor)} which returns the + * combined members for the base and remote. + *

                          + *

                          + * This method may be long running as a server may need to be contacted to + * obtain the members of the remote resource. + *

                          + *

                          + * This default implementation always returns null, but subclasses + * may override. + *

                          + * @param container the local container + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return the members of the remote resource corresponding to the given container + * @exception CoreException if the members could not be fetched. Reasons + * include: + *
                            + *
                          • The server could not be contacted for some reason.
                          • + *
                          • The remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                          • + *
                          + * @since 3.3 + */ + public IResource[] fetchRemoteMembers(IContainer container, IProgressMonitor monitor) throws CoreException { + //default implementation does nothing + //thwart compiler warning + return null; + } + + /** + * Return the list of projects that apply to this context. + * In other words, the context is only capable of querying the + * remote state for projects that are contained in the + * returned list. + * @return the list of projects that apply to this context + */ + public abstract IProject[] getProjects(); + + /** + * For three-way comparisons, this method indicates whether local + * modifications have been made to the given resource. For two-way + * comparisons, calling this method has the same effect as calling + * {@link #hasRemoteChange(IResource, IProgressMonitor)}. + * + * @param resource the resource being tested + * @param monitor a progress monitor + * @return whether the resource contains local modifications + * @exception CoreException if the contents could not be compared. Reasons + * include: + *
                            + *
                          • The server could not be contacted for some reason.
                          • + *
                          • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                          • + *
                          + */ + public abstract boolean hasLocalChange(IResource resource, IProgressMonitor monitor) throws CoreException; + + /** + * For two-way comparisons, return whether the contents of the corresponding + * remote differs from the content of the local file in the context of the + * current operation. By this we mean that this method will return + * true if the remote contents differ from the local + * contents. + *

                          + * For three-way comparisons, return whether the contents of the + * corresponding remote differ from the contents of the base. In other + * words, this method returns true if the corresponding + * remote has changed since the last time the local resource was updated + * with the remote contents. + *

                          + * For two-way comparisons, return true if the remote + * contents differ from the local contents. In this case, this method is + * equivalent to {@link #hasLocalChange(IResource, IProgressMonitor)} + *

                          + * This can be used by clients to determine if they need to fetch the remote + * contents in order to determine if the resources that constitute the model + * element are different in the remote location. If the local file exists + * and the remote file does not, or the remote file exists and the local + * does not then the contents will be said to differ (i.e. true + * is returned). Also, implementors will most likely use a timestamp based + * comparison to determine if the contents differ. This may lead to a + * situation where true is returned but the actual contents + * do not differ. Clients must be prepared handle this situation. + *

                          + * + * @param resource the local resource + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return whether the contents of the corresponding remote differ from the + * base. + * @exception CoreException if the contents could not be compared. Reasons + * include: + *
                            + *
                          • The server could not be contacted for some reason.
                          • + *
                          • The corresponding remote resource is not a container (status code + * will be {@link IResourceStatus#RESOURCE_WRONG_TYPE}).
                          • + *
                          + */ + public abstract boolean hasRemoteChange(IResource resource, IProgressMonitor monitor) throws CoreException; + + /** + * Return true if the context is associated with an operation + * that is using a three-way comparison and false if it is + * using a two-way comparison. + * + * @return whether the context is a three-way or two-way + */ + public abstract boolean isThreeWay(); + + /** + * Refresh the known remote state for any resources covered by the given + * traversals. Clients who require the latest remote state should invoke + * this method before invoking any others of the class. Mappings can use + * this method as a hint to the context provider of which resources will be + * required for the mapping to generate the proper set of traversals. + *

                          + * Note that this is really only a hint to the context provider. It is up to + * implementors to decide, based on the provided traversals, how to + * efficiently perform the refresh. In the ideal case, calls to + * {@link #hasRemoteChange(IResource, IProgressMonitor)} and + * {@link #fetchMembers} would not need to contact the server after a call to a + * refresh with appropriate traversals. Also, ideally, if + * {@link #FILE_CONTENTS_REQUIRED} is on of the flags, then the contents + * for these files will be cached as efficiently as possible so that calls to + * {@link #fetchRemoteContents} will also not need to contact the server. This + * may not be possible for all context providers, so clients cannot assume that + * the above mentioned methods will not be long running. It is still advisable + * for clients to call {@link #refresh} with as much details as possible since, in + * the case where a provider is optimized, performance will be much better. + *

                          + * + * @param traversals the resource traversals that indicate which resources + * are to be refreshed + * @param flags additional refresh behavior. For instance, if + * {@link #FILE_CONTENTS_REQUIRED} is one of the flags, this indicates + * that the client will be accessing the contents of the files covered by + * the traversals. {@link #NONE} should be used when no additional + * behavior is required + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if the refresh fails. Reasons include: + *
                            + *
                          • The server could not be contacted for some reason.
                          • + *
                          + */ + public abstract void refresh(ResourceTraversal[] traversals, int flags, IProgressMonitor monitor) throws CoreException; +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java new file mode 100644 index 0000000000..6278a8b1b5 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceChangeValidator.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2006, 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.ArrayList; +import java.util.List; +import org.eclipse.core.internal.resources.mapping.ChangeDescription; +import org.eclipse.core.internal.resources.mapping.ResourceChangeDescriptionFactory; +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 resource change validator is used to validate that changes made to + * resources will not adversely affect the models stored in those resources. + *

                          + * The validator is used by first creating a resource delta describing the + * proposed changes. A delta can be generated using a {@link IResourceChangeDescriptionFactory}. + * The change is then validated by calling the {@link #validateChange(IResourceDelta, IProgressMonitor)} + * method. This example validates a change to a single file: + * + * IFile file = ..;//some file that is going to be changed + * ResourceChangeValidator validator = ResourceChangeValidator.getValidator(); + * IResourceChangeDescriptionFactory factory = validator.createDeltaFactory(); + * factory.change(file); + * IResourceDelta delta = factory.getDelta(); + * IStatus result = validator.validateChange(delta, null); + * + * If the result status does not have severity {@link IStatus#OK}, then + * the changes may cause problems for models that are built on those + * resources. In this case the user should be presented with the status message + * to determine if they want to proceed with the modification. + *

                          + * + * @since 3.2 + */ +public final class ResourceChangeValidator { + private static ResourceChangeValidator instance; + + /** + * Return the singleton change validator. + * @return the singleton change validator + */ + public static ResourceChangeValidator getValidator() { + if (instance == null) + instance = new ResourceChangeValidator(); + return instance; + } + + /** + * Singleton accessor method should be used instead. + * @see #getValidator() + */ + private ResourceChangeValidator() { + super(); + } + + private IStatus combineResults(IStatus[] result) { + List notOK = new ArrayList<>(); + for (int i = 0; i < result.length; i++) { + IStatus status = result[i]; + if (!status.isOK()) { + notOK.add(status); + } + } + if (notOK.isEmpty()) { + return Status.OK_STATUS; + } + if (notOK.size() == 1) { + return notOK.get(0); + } + return new MultiStatus(ResourcesPlugin.PI_RESOURCES, 0, notOK.toArray(new IStatus[notOK.size()]), Messages.mapping_multiProblems, null); + } + + /** + * Return an empty change description factory that can be used to build a + * proposed resource delta. + * @return an empty change description factory that can be used to build a + * proposed resource delta + */ + public IResourceChangeDescriptionFactory createDeltaFactory() { + return new ResourceChangeDescriptionFactory(); + } + + private ModelProvider[] getProviders(IResource[] resources) { + IModelProviderDescriptor[] descriptors = ModelProvider.getModelProviderDescriptors(); + List result = new ArrayList<>(); + for (int i = 0; i < descriptors.length; i++) { + IModelProviderDescriptor descriptor = descriptors[i]; + try { + IResource[] matchingResources = descriptor.getMatchingResources(resources); + if (matchingResources.length > 0) { + result.add(descriptor.getModelProvider()); + } + } catch (CoreException e) { + Policy.log(e.getStatus().getSeverity(), NLS.bind("Could not instantiate provider {0}", descriptor.getId()), e); //$NON-NLS-1$ + } + } + return result.toArray(new ModelProvider[result.size()]); + } + + /* + * Get the roots of any changes. + */ + private IResource[] getRootResources(IResourceDelta root) { + final ChangeDescription changeDescription = new ChangeDescription(); + try { + root.accept(new IResourceDeltaVisitor() { + @Override + public boolean visit(IResourceDelta delta) { + return changeDescription.recordChange(delta); + } + }); + } catch (CoreException e) { + // Shouldn't happen since the ProposedResourceDelta accept doesn't throw an + // exception and our visitor doesn't either + Policy.log(IStatus.ERROR, "Internal error", e); //$NON-NLS-1$ + } + return changeDescription.getRootResources(); + } + + /** + * Validate the proposed changes contained in the given delta + * by consulting all model providers to determine if the changes + * have any adverse side effects. + *

                          + * This method returns either a {@link ModelStatus}, or a {@link MultiStatus} + * whose children are {@link ModelStatus}. In either case, the severity + * of the status indicates the severity of the possible side-effects of + * the operation. Any severity other than OK should be + * shown to the user. The message should be a human readable message that + * will allow the user to make a decision on whether to continue with the + * operation. The model provider id should indicate which model is flagging the + * the possible side effects. + *

                          + * + * @param delta a delta tree containing the proposed changes + * @return a status indicating any potential side effects + * on models stored in the affected resources. + */ + public IStatus validateChange(IResourceDelta delta, IProgressMonitor monitor) { + monitor = Policy.monitorFor(monitor); + try { + IResource[] resources = getRootResources(delta); + ModelProvider[] providers = getProviders(resources); + if (providers.length == 0) + return Status.OK_STATUS; + monitor.beginTask(Messages.mapping_validate, providers.length); + IStatus[] result = new IStatus[providers.length]; + for (int i = 0; i < providers.length; i++) + result[i] = providers[i].validateChange(delta, Policy.subMonitorFor(monitor, 1)); + return combineResults(result); + } finally { + monitor.done(); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java new file mode 100644 index 0000000000..725486090f --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMapping.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.ArrayList; +import org.eclipse.core.internal.resources.mapping.ModelProviderManager; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * A resource mapping supports the transformation of an application model + * object into its underlying file system resources. It provides the + * bridge between a logical element and the physical resource(s) into which it + * is stored but does not provide more comprehensive model access or + * manipulations. + *

                          + * Mappings provide two means of model traversal. The {@link #accept} method + * can be used to visit the resources that constitute the model object. Alternatively, + * a set or traversals can be obtained by calling {@link #getTraversals}. A traversal + * contains a set of resources and a depth. This allows clients (such a repository providers) + * to do optimal traversals of the resources w.r.t. the operation that is being performed + * on the model object. + *

                          + *

                          + * This class may be subclassed by clients. + *

                          + + * @see IResource + * @see ResourceTraversal + * @since 3.2 + */ +public abstract class ResourceMapping extends PlatformObject { + + /** + * Accepts the given visitor for all existing resources in this mapping. + * The visitor's {@link IResourceVisitor#visit} method is called for each + * accessible resource in this mapping. + * + * @param context the traversal context + * @param visitor the visitor + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception CoreException if this method fails. Reasons include: + *
                            + *
                          • The visitor failed with this exception.
                          • + *
                          • The traversals for this mapping could not be obtained.
                          • + *
                          + */ + public void accept(ResourceMappingContext context, IResourceVisitor visitor, IProgressMonitor monitor) throws CoreException { + ResourceTraversal[] traversals = getTraversals(context, monitor); + for (int i = 0; i < traversals.length; i++) + traversals[i].accept(visitor); + } + + /** + * Return whether this resource mapping contains all the resources + * of the given mapping. + *

                          + * This method always returns false when the given resource + * mapping's model provider id does not match that the of the receiver. + *

                          + * + * @param mapping the given resource mapping + * @return true if this mapping contains all the resources + * of the given mapping, and false otherwise. + */ + public boolean contains(ResourceMapping mapping) { + return false; + } + + /** + * Override equals to compare the model objects of the + * mapping in order to determine equality. + * @param obj the object to compare + * @return true if the receiver is equal to the + * given object, and false otherwise. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj instanceof ResourceMapping) { + ResourceMapping other = (ResourceMapping) obj; + return other.getModelObject().equals(getModelObject()); + } + return false; + } + + /** + * Returns all markers of the specified type on the resources in this mapping. + * If includeSubtypes is false, only markers + * whose type exactly matches the given type are returned. Returns an empty + * array if there are no matching markers. + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return an array of markers + * @exception CoreException if this method fails. + */ + public IMarker[] findMarkers(String type, boolean includeSubtypes, IProgressMonitor monitor) throws CoreException { + final ResourceTraversal[] traversals = getTraversals(ResourceMappingContext.LOCAL_CONTEXT, monitor); + ArrayList result = new ArrayList<>(); + for (int i = 0; i < traversals.length; i++) + traversals[i].doFindMarkers(result, type, includeSubtypes); + return result.toArray(new IMarker[result.size()]); + } + + /** + * Returns the application model element associated with this + * resource mapping. + * + * @return the application model element associated with this + * resource mapping. + */ + public abstract Object getModelObject(); + + /** + * Return the model provider for the model object + * of this resource mapping. The model provider is obtained + * using the id returned from getModelProviderId(). + * @return the model provider + */ + public final ModelProvider getModelProvider() { + try { + return ModelProviderManager.getDefault().getModelProvider(getModelProviderId()); + } catch (CoreException e) { + throw new IllegalStateException(e.getMessage()); + } + } + + /** + * Returns the id of the model provider that generated this resource + * mapping. + * + * @return the model provider id + */ + public abstract String getModelProviderId(); + + /** + * Returns the projects that contain the resources that constitute this + * application model. + * + * @return the projects + */ + public abstract IProject[] getProjects(); + + /** + * Returns one or more traversals that can be used to access all the + * physical resources that constitute the logical resource. A traversal is + * simply a set of resources and the depth to which they are to be + * traversed. This method returns an array of traversals in order to provide + * flexibility in describing the traversals that constitute a model element. + *

                          + * Subclasses should, when possible, include + * all resources that are or may be members of the model element. + * For instance, a model element should return the same list of + * resources regardless of the existence of the files on the file system. + * For example, if a logical resource called "form" maps to "/p1/form.xml" + * and "/p1/form.java" then whether form.xml or form.java existed, they + * should be returned by this method. + *

                          + * In some cases, it may not be possible for a model element to know all the + * resources that may constitute the element without accessing the state of + * the model element in another location (e.g. a repository). This method is + * provided with a context which, when provided, gives access to + * the members of corresponding remote containers and the contents of + * corresponding remote files. This gives the model element the opportunity + * to deduce what additional resources should be included in the traversal. + *

                          + * + * @param context gives access to the state of + * remote resources that correspond to local resources for the + * purpose of determining traversals that adequately cover the + * model element resources given the state of the model element + * in another location. This parameter may be null, in + * which case the implementor can assume that only the local + * resources are of interest to the client. + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @return a set of traversals that cover the resources that constitute the + * model element + * @exception CoreException if the traversals could not be obtained. + */ + public abstract ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) throws CoreException; + + /** + * Override hashCode to use the model object. + */ + @Override + public int hashCode() { + return getModelObject().hashCode(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java new file mode 100644 index 0000000000..c0a0cae230 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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 Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +/** + * A resource mapping context is provided to a resource mapping when traversing + * the resources of the mapping. The type of context may determine what resources + * are included in the traversals of a mapping. + *

                          + * There are currently two resource mapping contexts: the local mapping context + * (represented by the singleton {@link #LOCAL_CONTEXT}), + * and {@link RemoteResourceMappingContext}. Implementors of {@link ResourceMapping} + * should not assume that these are the only valid contexts (in order to allow future + * extensibility). Therefore, if the provided context is not of one of the above mentioned types, + * the implementor can assume that the context is a local context. + *

                          + *

                          + * This class may be subclassed by clients; this class is not intended to be + * instantiated directly. + *

                          + * + * @see ResourceMapping + * @see RemoteResourceMappingContext + * @since 3.2 + */ +public class ResourceMappingContext { + + /** + * This resource mapping context is used to indicate that the operation + * that is requesting the traversals is performing a local operation. + * Because the operation is local, the resource mapping is free to be + * as precise as desired about what resources make up the mapping without + * concern for performing optimized remote operations. + */ + public static final ResourceMappingContext LOCAL_CONTEXT = new ResourceMappingContext(); + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java new file mode 100644 index 0000000000..8e244d6d17 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java @@ -0,0 +1,183 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + *******************************************************************************/ +package org.eclipse.core.resources.mapping; + +import java.util.ArrayList; +import org.eclipse.core.internal.resources.MarkerManager; +import org.eclipse.core.internal.resources.Workspace; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.CoreException; + +/** + * A resource traversal is simply a set of resources and the depth to which + * each is to be traversed. A set of traversals is used to describe the + * resources that constitute a model element. + *

                          + * The flags of the traversal indicate which special resources should be + * included or excluded from the traversal. The flags used are the same as + * those passed to the {@link IResource#accept(IResourceVisitor, int, int)} method. + * + *

                          + * This class may be instantiated or subclassed by clients. + *

                          + + * @see org.eclipse.core.resources.IResource + * @since 3.2 + */ +public class ResourceTraversal { + + private final int depth; + private final int flags; + private final IResource[] resources; + + /** + * Creates a new resource traversal. + * @param resources The resources in the traversal + * @param depth The traversal depth + * @param flags the flags for this traversal. The traversal flags match those + * that are passed to the IResource#accept method. + */ + public ResourceTraversal(IResource[] resources, int depth, int flags) { + if (resources == null) + throw new NullPointerException(); + this.resources = resources; + this.depth = depth; + this.flags = flags; + } + + /** + * Visits all existing resources defined by this traversal. + * + * @param visitor a resource visitor + * @exception CoreException if this method fails. Reasons include: + *
                            + *
                          • The visitor failed with this exception.
                          • + *
                          + */ + public void accept(IResourceVisitor visitor) throws CoreException { + for (int i = 0, imax = resources.length; i < imax; i++) + try { + if (resources[i].exists()) + resources[i].accept(visitor, depth, flags); + } catch (CoreException e) { + //ignore failure in the case of concurrent deletion + if (e.getStatus().getCode() != IResourceStatus.RESOURCE_NOT_FOUND) + throw e; + } + } + + /** + * Return whether the given resource is contained in or + * covered by this traversal, regardless of whether the resource + * currently exists. + * + * @param resource the resource to be tested + * @return true if the resource is in this traversal, and + * false otherwise. + */ + public boolean contains(IResource resource) { + for (int i = 0; i < resources.length; i++) { + IResource member = resources[i]; + if (contains(member, resource)) { + return true; + } + } + return false; + } + + private boolean contains(IResource resource, IResource child) { + if (resource.equals(child)) + return true; + if (depth == IResource.DEPTH_ZERO) + return false; + if (child.getParent().equals(resource)) + return true; + if (depth == IResource.DEPTH_INFINITE) + return resource.getFullPath().isPrefixOf(child.getFullPath()); + return false; + } + + /** + * Efficient implementation of {@link #findMarkers(String, boolean)}, not + * available to clients because underlying non-API methods are used that + * may change. + */ + void doFindMarkers(ArrayList result, String type, boolean includeSubtypes) { + MarkerManager markerMan = ((Workspace) ResourcesPlugin.getWorkspace()).getMarkerManager(); + for (int i = 0; i < resources.length; i++) + markerMan.doFindMarkers(resources[i], result, type, includeSubtypes, depth); + } + + /** + * Returns all markers of the specified type on existing resources in this traversal. + * If includeSubtypes is false, only markers + * whose type exactly matches the given type are returned. Returns an empty + * array if there are no matching markers. + * + * @param type the type of marker to consider, or null to indicate all types + * @param includeSubtypes whether or not to consider sub-types of the given type + * @return an array of markers + * @exception CoreException if this method fails. + * @see IResource#findMarkers(String, boolean, int) + */ + public IMarker[] findMarkers(String type, boolean includeSubtypes) throws CoreException { + if (resources.length == 0) + return new IMarker[0]; + ArrayList result = new ArrayList<>(); + doFindMarkers(result, type, includeSubtypes); + return result.toArray(new IMarker[result.size()]); + } + + /** + * Returns the depth to which the resources should be traversed. + * + * @return the depth to which the physical resources are to be traversed + * (one of IResource.DEPTH_ZERO, IResource.DEPTH_ONE or + * IResource.DEPTH_INFINITE) + */ + public int getDepth() { + return depth; + } + + /** + * Return the flags for this traversal. + * The flags of the traversal indicate which special resources should be + * included or excluded from the traversal. The flags used are the same as + * those passed to the IResource#accept(IResourceVisitor, int, int) method. + * Clients who traverse the resources manually (i.e. without calling accept) + * should respect the flags when determining which resources are included + * in the traversal. + * + * @return the flags for this traversal + */ + public int getFlags() { + return flags; + } + + /** + * Returns the file system resource(s) for this traversal. The returned + * resources must be contained within the same project and need not exist in + * the local file system. The traversal of the returned resources should be + * done considering the flag returned by getDepth. If a resource returned by + * a traversal is a file, it should always be visited. If a resource of a + * traversal is a folder then files contained in the folder can only be + * visited if the folder is IResource.DEPTH_ONE or IResource.DEPTH_INFINITE. + * Child folders should only be visited if the depth is + * IResource.DEPTH_INFINITE. + * + * @return The resources in this traversal + */ + public IResource[] getResources() { + return resources; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html new file mode 100644 index 0000000000..c3214612a1 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/package.html @@ -0,0 +1,27 @@ + + + + + + + Package-level Javadoc + + +Provides APIs for integrating application models with the workspace + +

                          Package Specification

                          +

                          +This package specifies the APIs in the Resources plug-in that are used to integrate +application models with the workspace. This API introduces the notion of a +ResourceMapping that defines the relationship between an application +model object and a set of underlying resources, and a ResourceTraversal +that describes the exact resources corresponding to a given application model object. +The relationship between an application model and underlying resources can vary +depending a context. This notion is captured by ResourceMappingContext +and its subclasses. +

                          +@since 3.2 +

                          + + + diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html new file mode 100644 index 0000000000..6fe0b74733 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/package.html @@ -0,0 +1,25 @@ + + + + + + + Package-level Javadoc + + +Provides basic support for managing a workspace and +its resources. +

                          +Package Specification

                          +This package specifies the principal API for the Resources plug-in.  +The resources plug-in defines the notions of Workspaces and Resources.  +The workspace's resource model is very similar to a file system.  +All resources are backed by a real file or directory in some backing file +system.  They are stored in their native form (i.e., no extra bytes +or markup) using their normal names. +

                          In addition to basic resource management, the Resources plug-in supports +various workspace lifecycle events such as save and snapshot, and resource +change events. +
                            + + diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java new file mode 100644 index 0000000000..221ed6d40e --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 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.resources.refresh; + +import org.eclipse.core.resources.IResource; + +/** + * An IRefreshMonitor monitors trees of IResources + * for changes in the local file system. + *

                          + * When an IRefreshMonitor notices changes, it should report them + * to the IRefreshResult provided at the time of the monitor's + * creation. + *

                          + * Clients may implement this interface. + *

                          + * + * @since 3.0 + */ +public interface IRefreshMonitor { + /** + * Informs the monitor that it should stop monitoring the given resource. + * + * @param resource the resource that should no longer be monitored, or + * null if this monitor should stop monitoring all resources + * it is currently monitoring + */ + public void unmonitor(IResource resource); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java new file mode 100644 index 0000000000..5143dabca3 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 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.resources.refresh; + +import org.eclipse.core.resources.IResource; + +/** + * An IRefreshResult is provided to an auto-refresh + * monitor. The result is used to submit resources to be refreshed, and + * for reporting failure of the monitor. + * + * @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 IRefreshResult { + /** + * Notifies that the given monitor has encountered a failure from which it + * cannot recover while monitoring the given resource. + *

                          + * If the given resource is null it indicates that the + * monitor has failed completely, and the refresh manager will have to + * take over the monitoring responsibilities for all resources that the + * monitor was monitoring. + * + * @param monitor a monitor which has encountered a failure that it + * cannot recover from + * @param resource the resource that the monitor can no longer + * monitor, or null to indicate that the monitor can no + * longer monitor any of the resources it was monitoring + */ + public void monitorFailed(IRefreshMonitor monitor, IResource resource); + + /** + * Requests that the provided resource be refreshed. The refresh will + * occur in the background during the next scheduled refresh. + * + * @param resource the resource to refresh + */ + public void refresh(IResource resource); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java new file mode 100644 index 0000000000..d0f87e551d --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.resources.refresh; + +import org.eclipse.core.internal.refresh.InternalRefreshProvider; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; + +/** + * The abstract base class for all auto-refresh providers. This class provides + * the infrastructure for defining an auto-refresh provider and fulfills the + * contract specified by the org.eclipse.core.resources.refreshProviders + * standard extension point. + *

                          + * All auto-refresh providers must subclass this class. A + * RefreshProvider is responsible for creating + * IRefreshMonitor objects. The provider must decide if + * it is capable of monitoring the file, or folder and subtree under the path that is provided. + *

                          + * Note: since 3.12, all subclasses should override {@link #installMonitor(IResource, IRefreshResult, IProgressMonitor)} + * instead of {@link #installMonitor(IResource, IRefreshResult)}. + * @since 3.0 + */ +public abstract class RefreshProvider extends InternalRefreshProvider { + /** + * Creates a new refresh monitor that performs naive polling of the resource + * in the file system to detect changes. The returned monitor will immediately begin + * monitoring the specified resource root and report changes back to the workspace. + *

                          + * This default monitor can be returned by subclasses when + * installMonitor is called. + *

                          + * If the returned monitor is not immediately returned from the installMonitor + * method, then clients are responsible for telling the returned monitor to + * stop polling when it is no longer needed. The returned monitor can be told to + * stop working by invoking IRefreshMonitor.unmonitor(IResource). + * + * @param resource The resource to begin monitoring + * @return A refresh monitor instance + * @see #installMonitor(IResource, IRefreshResult) + */ + @Override + protected IRefreshMonitor createPollingMonitor(IResource resource) { + return super.createPollingMonitor(resource); + } + + /** + * @deprecated Subclasses should override and clients should call + * {@link #installMonitor(IResource, IRefreshResult, IProgressMonitor)} instead. + * @see #installMonitor(IResource, IRefreshResult, IProgressMonitor) + */ + @Deprecated + public IRefreshMonitor installMonitor(IResource resource, IRefreshResult result) { + return null; + } + + /** + * Returns an IRefreshMonitor that will monitor a resource. If + * the resource is an IContainer the monitor will also + * monitor the subtree under the container. Returns null if + * this provider cannot create a monitor for the given resource. The + * provider may return the same monitor instance that has been provided for + * other resources. + *

                          + * The monitor should send results and failures to the provided refresh + * result. + * + * @param resource the resource to monitor + * @param result the result callback for notifying of failure or of resources that need + * refreshing + * @param progressMonitor the progress monitor to use for reporting progress to the user. + * It is the caller's responsibility to call done() on the given monitor. Accepts null, + * indicating that no progress should be reported and that the operation cannot be cancelled. + * @return a monitor on the resource, or null + * if the resource cannot be monitored + * @see #createPollingMonitor(IResource) + * @since 3.12 + */ + public IRefreshMonitor installMonitor(IResource resource, IRefreshResult result, IProgressMonitor progressMonitor) { + return installMonitor(resource, result); + } + + /** + * @deprecated Subclasses should override and clients should call + * {@link #resetMonitors(IResource, IProgressMonitor)} instead. + */ + @Deprecated + public void resetMonitors(IResource resource) { + this.resetMonitors(resource, new NullProgressMonitor()); + } + + /** + * Resets the installed monitors for the given resource. This will remove all + * existing monitors that are installed on the resource, and then ask all + * refresh providers to begin monitoring the resource again. + *

                          + * This method is intended to be used by refresh providers that need to change + * the refresh monitor that they previously used to monitor a resource. + * + * @param resource The resource to reset the monitors for + * @param progressMonitor the progress monitor to use for reporting progress to the user. + * It is the caller's responsibility to call done() on the given monitor. Accepts null, + * indicating that no progress should be reported and that the operation cannot be cancelled. + * @since 3.12 + */ + @Override + public void resetMonitors(IResource resource, IProgressMonitor progressMonitor) { + super.resetMonitors(resource, progressMonitor); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html new file mode 100644 index 0000000000..4d3a2fe39a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/package.html @@ -0,0 +1,23 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the auto-refresh providers. + +

                          Package Specification

                          +

                          +This package specifies the APIs in the Resources plug-in that are intended +to be implemented and used by plug-ins using the refreshProviders extension point. +This extension point is used by plug-ins to notify the workspace of changes that +have occurred externally in the file system. +

                          +@since 3.0 +

                          + + + diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java new file mode 100644 index 0000000000..4e7d3c2d8a --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ +package org.eclipse.core.resources.team; + +import org.eclipse.core.resources.IWorkspace; + +/** + * A context that is used in conjunction with the {@link FileModificationValidator} + * to indicate that UI-based validation is desired. + *

                          + * This class is not intended to be instantiated or subclassed by clients. + * + * @see FileModificationValidator + * @since 3.3 + */ +public class FileModificationValidationContext { + + /** + * Constant that can be passed to {@link IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], Object)} + * to indicate that the caller does not have access to a UI context but would still + * like to have UI-based validation if possible. + */ + public static final FileModificationValidationContext VALIDATE_PROMPT = new FileModificationValidationContext(null); + + private final Object shell; + + /** + * Create a context with the given shell. + * + * @param shell the shell + */ + FileModificationValidationContext(Object shell) { + this.shell = shell; + } + + /** + * Return the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null if there is no UI context + * available (declared as an Object to avoid any direct references on the SWT component). + * If there is no shell, the {@link FileModificationValidator} may still perform + * UI-based validation if they can obtain a Shell from another source. + * @return the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null + */ + public Object getShell() { + return shell; + } +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java new file mode 100644 index 0000000000..e56bc2b5a9 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.resources.team; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IStatus; + +/** + * The file modification validator is a Team-related hook for pre-checking operations + * that modify the contents of files. + *

                          + * This class is used only in conjunction with the + * "org.eclipse.core.resources.fileModificationValidator" + * extension point. It is intended to be implemented only + * by the Eclipse Platform Team plug-in or by repository providers + * whose validator get invoked by Team. + *

                          + * @since 3.3 + */ +@SuppressWarnings("deprecation") +public abstract class FileModificationValidator implements IFileModificationValidator { + + /** + * Validates that the given files can be modified. The files must all exist + * in the workspace. The optional context object may be supplied if + * UI-based validation is required. If the context is null, the + * validator must attempt to perform the validation in a headless manner. + * The returned status is IStatus.OK if this validator + * believes the given file can be modified. Other return statuses indicate + * the reason why the individual files cannot be modified. + * + * @param files the files that are to be modified; these files must all exist in the workspace + * @param context the org.eclipse.swt.widgets.Shell that is to be used to + * parent any dialogs with the user, or null if there is no UI context (declared + * as an Object to avoid any direct references on the SWT component) + * @return a status object that is OK if things are fine, otherwise a status describing + * reasons why modifying the given files is not reasonable + * @see IWorkspace#validateEdit(IFile[], Object) + * @deprecated this method is part of the deprecated {@link IFileModificationValidator} + * interface. Clients should call {@link #validateEdit(IFile[], FileModificationValidationContext)} + * instead. + */ + @Deprecated + @Override + public final IStatus validateEdit(IFile[] files, Object context) { + FileModificationValidationContext validationContext; + if (context == null) + validationContext = null; + else if (context instanceof FileModificationValidationContext) + validationContext = (FileModificationValidationContext) context; + else + validationContext = new FileModificationValidationContext(context); + return validateEdit(files, validationContext); + } + + /** + * Validates that the given file can be saved. This method is called from + * IFile#setContents and IFile#appendContents + * before any attempt to write data to disk. The returned status is + * IStatus.OK if this validator believes the given file can be + * successfully saved. In all other cases the return value is a non-OK status. + * Note that a return value of IStatus.OK does not guarantee + * that the save will succeed. + * + * @param file the file that is to be modified; this file must exist in the workspace + * @return a status indicating whether or not it is reasonable to try writing to the given file; + * IStatus.OK indicates a save should be attempted. + * + * @see IFile#setContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + * @see IFile#appendContents(java.io.InputStream, int, org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public IStatus validateSave(IFile file) { + return validateEdit(new IFile[] {file}, (FileModificationValidationContext) null); + } + + /** + * Validates that the given files can be modified. The files must all exist + * in the workspace. The optional context may be supplied if + * UI-based validation is required. If the context is null, the + * validator must attempt to perform the validation in a headless manner. + * The returned status is IStatus.OK if this validator + * believes the given file can be modified. Other return statuses indicate + * the reason why the individual files cannot be modified. + * + * @param files the files that are to be modified; these files must all exist in the workspace + * @param context the context to aid in UI-based validation or null if the validation + * must be headless + * @return a status object that is OK if things are fine, otherwise a status describing + * reasons why modifying the given files is not reasonable + * @see IWorkspace#validateEdit(IFile[], Object) + */ + public abstract IStatus validateEdit(IFile[] files, FileModificationValidationContext context); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java new file mode 100644 index 0000000000..1567a3b2c7 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java @@ -0,0 +1,383 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.team; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +/** + * Primary interface for hooking the implementation of + * IResource.move and IResource.delete. + *

                          + * This interface is intended to be implemented by the team component in + * conjunction with the org.eclipse.core.resources.moveDeleteHook + * standard extension point. Individual team providers may also implement this + * interface. It is not intended to be implemented by other clients. The methods + * defined on this interface are called from within the implementations of + * IResource.move and IResource.delete. They are not + * intended to be called from anywhere else. + *

                          + * + * @since 2.0 + */ +public interface IMoveDeleteHook { + + /** + * Implements IResource.delete(int,IProgressMonitor) where the + * receiver is a file. Returns true to accept responsibility + * for implementing this operation as per the API contract. + *

                          + * In broad terms, a full re-implementation should delete the file in the + * local file system and then call tree.deletedFile to complete + * the updating of the workspace resource tree to reflect this fact. If + * unsuccessful in deleting the file from the local file system, it + * should instead call tree.failed to report the reason for + * the failure. In either case, it should return true to + * indicate that the operation was attempted. The FORCE update + * flag needs to be honored: unless FORCE is specified, the + * implementation must use tree.isSynchronized to determine + * whether the file is in sync before attempting to delete it. + * The KEEP_HISTORY update flag needs to be honored as well; + * use tree.addToLocalHistory to capture the contents of the + * file before deleting it from the local file system. + *

                          + * An extending implementation should perform whatever pre-processing it + * needs to do and then call tree.standardDeleteFile to + * explicitly invoke the standard file deletion behavior, which deletes + * both the file from the local file system and updates the workspace + * resource tree. It should return true to indicate that the + * operation was attempted. + *

                          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteFile and returning true. + *

                          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param file the handle of the file to delete; the receiver of + * IResource.delete(int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this method + * attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public boolean deleteFile(IResourceTree tree, IFile file, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.delete(int,IProgressMonitor) where the + * receiver is a folder. Returns true to accept responsibility + * for implementing this operation as per the API contract. + *

                          + * In broad terms, a full re-implementation should delete the directory tree + * in the local file system and then call tree.deletedFolder to + * complete the updating of the workspace resource tree to reflect this fact. + * If unsuccessful in deleting the directory or any of its descendents from + * the local file system, it should instead call tree.failed to + * report each reason for failure. In either case it should return + * true to indicate that the operation was attempted. + * The FORCE update flag needs to be honored: unless + * FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the folder + * subtree is in sync before attempting to delete it. + * The KEEP_HISTORY update flag needs to be honored as well; + * use tree.addToLocalHistory to capture the contents of any + * files being deleted. + *

                          + * A partial re-implementation should perform whatever pre-processing it + * needs to do and then call tree.standardDeleteFolder to + * explicitly invoke the standard folder deletion behavior, which deletes + * both the folder and its descendents from the local file system and + * updates the workspace resource tree. It should return true + * to indicate that the operation was attempted. + *

                          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteFolder and returning true. + *

                          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param folder the handle of the folder to delete; the receiver of + * IResource.delete(int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public boolean deleteFolder(IResourceTree tree, IFolder folder, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.delete(int,IProgressMonitor) where the + * receiver is a project. Returns true to accept responsibility + * for implementing this operation as per the API contract. + *

                          + * In broad terms, a full re-implementation should delete the project content area in + * the local file system if required (the files of a closed project should be deleted + * only if the IResource.ALWAYS_DELETE_PROJECT_CONTENTS update + * flag is specified; the files of an open project should be deleted unless the + * the IResource.NEVER_DELETE_PROJECT_CONTENTS update flag is + * specified). It should then call tree.deletedProject to complete + * the updating of the workspace resource tree to reflect this fact. If unsuccessful + * in deleting the project's files from the local file system, it should instead call + * tree.failed to report the reason for the failure. In either case, it + * should return true to indicate that the operation was attempted. + * The FORCE update flag may need to be honored if the project is open: + * unless FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the project subtree is in + * sync before attempting to delete it. + * Note that local history is not maintained when a project is deleted, + * regardless of the setting of the KEEP_HISTORY update flag. + *

                          + * A partial re-implementation should perform whatever pre-processing it needs + * to do and then call tree.standardDeleteProject to explicitly + * invoke the standard project deletion behavior. It should return true + * to indicate that the operation was attempted. + *

                          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteProject and returning true. + *

                          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param project the handle of the project to delete; the receiver of + * IResource.delete(int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#delete(int,IProgressMonitor) + */ + public boolean deleteProject(IResourceTree tree, IProject project, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.move(IPath,int,IProgressMonitor) where + * the receiver is a file. Returns true to accept + * responsibility for implementing this operation as per the API contract. + *

                          + * On entry to this hook method, the following is guaranteed about the + * workspace resource tree: the source file exists; the destination file + * does not exist; the container of the destination file exists and is + * accessible. In broad terms, a full re-implementation should move the file + * in the local file system and then call tree.moveFile to + * complete the updating of the workspace resource tree to reflect this + * fact. If unsuccessful in moving the file in the local file system, + * it should instead call tree.failed to report the reason for + * the failure. In either case, it should return true to + * indicate that the operation was attempted. + * The FORCE update flag needs to be honored: unless + * FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the file is in sync before + * attempting to move it. + * The KEEP_HISTORY update flag needs to be honored as well; use + * tree.addToLocalHistory to capture the contents of the file + * (naturally, this must be before moving the file from the local file system). + *

                          + * An extending implementation should perform whatever pre-processing it needs + * to do and then call tree.standardMoveFile to explicitly + * invoke the standard file moving behavior, which moves both the file in the + * local file system and updates the workspace resource tree. It should return + * true to indicate that the operation was attempted. + *

                          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardMoveFile and returning true. + *

                          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param source the handle of the file to move; the receiver of + * IResource.move(IPath,int,IProgressMonitor) + * @param destination the handle of where the file will move to; the handle + * equivalent of the first parameter to + * IResource.move(IPath,int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(org.eclipse.core.runtime.IPath,int,IProgressMonitor) + */ + public boolean moveFile(IResourceTree tree, IFile source, IFile destination, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.move(IPath,int,IProgressMonitor) where + * the receiver is a project. Returns true to accept + * responsibility for implementing this operation as per the API contract. + *

                          + * On entry to this hook method, the following is guaranteed about the + * workspace resource tree: the source folder exists; the destination folder + * does not exist; the container of the destination folder exists and is + * accessible. In broad terms, a full re-implementation should move the + * directory tree in the local file system and then call + * tree.movedFolder to complete the updating of the workspace + * resource tree to reflect this fact. If unsuccessful in moving the + * directory or any of its descendents in the local file system, + * call tree.failed to report each reason for failure. + * In either case, return true to indicate that the operation + * was attempted. + * The FORCE update flag needs to be honored: unless + * FORCE is specified, the implementation must use + * tree.isSynchronized to determine whether the folder subtree is in sync + * before attempting to move it. + * The KEEP_HISTORY update flag needs to be honored as well; use + * tree.addToLocalHistory to capture the contents of any files being + * moved. + *

                          + * A partial re-implementation should perform whatever pre-processing it needs + * to do and then call tree.standardMoveFolder to explicitly + * invoke the standard folder move behavior, which move both the folder + * and its descendents in the local file system and updates the workspace resource + * tree. Return true to indicate that the operation was attempted. + *

                          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardDeleteFolder and returning true. + *

                          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param source the handle of the folder to move; the receiver of + * IResource.move(IPath,int,IProgressMonitor) + * @param destination the handle of where the folder will move to; the + * handle equivalent of the first parameter to + * IResource.move(IPath,int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this + * method attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(org.eclipse.core.runtime.IPath,int,IProgressMonitor) + */ + public boolean moveFolder(IResourceTree tree, IFolder source, IFolder destination, int updateFlags, IProgressMonitor monitor); + + /** + * Implements IResource.move(IPath,int,IProgressMonitor) and + * IResource.move(IProjectDescription,int,IProgressMonitor) + * where the receiver is a project. Returns true to accept + * responsibility for implementing this operation as per the API contracts. + *

                          + * On entry to this hook method, the source project is guaranteed to exist + * and be open in the workspace resource tree. If the given description + * contains a different name from that of the given project, then the + * project is being renamed (and its content possibly relocated). If the + * given description contains the same name as the given project, then the + * project is being relocated but not renamed. When the project is being + * renamed, the destination project is guaranteed not to exist in the + * workspace resource tree. + *

                          + * Returning false is the easy way for the implementation to + * say "pass". It is equivalent to calling + * tree.standardMoveProject and returning true. + *

                          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                          + * + * @param tree the workspace resource tree; this object is only valid + * for the duration of the invocation of this method, and must not be + * used after this method has completed + * @param source the handle of the open project to move; the receiver of + * IResource.move(IProjectDescription,int,IProgressMonitor) + * or IResource.move(IPath,int,IProgressMonitor) + * @param description the new description of the project; the first + * parameter to + * IResource.move(IProjectDescription,int,IProgressMonitor), or + * a copy of the project's description with the location changed to the + * path given in the first parameter to + * IResource.move(IPath,int,IProgressMonitor) + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IProjectDescription,int,IProgressMonitor) + * or IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IProjectDescription,int,IProgressMonitor) + * or IResource.move(IPath,int,IProgressMonitor) + * @return false if this method declined to assume + * responsibility for this operation, and true if this method + * attempted to carry out the operation + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see IResource#move(org.eclipse.core.runtime.IPath,int,IProgressMonitor) + * @see IResource#move(IProjectDescription,int,IProgressMonitor) + */ + public boolean moveProject(IResourceTree tree, IProject source, IProjectDescription description, int updateFlags, IProgressMonitor monitor); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java new file mode 100644 index 0000000000..5211682459 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java @@ -0,0 +1,383 @@ +/******************************************************************************* + * 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.resources.team; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; + +/** + * Provides internal access to the workspace resource tree for the purposes of + * implementing the move and delete operations. Implementations of + * IMoveDeleteHook call these methods. + * + * @since 2.0 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IResourceTree { + + /** + * Constant indicating that no file timestamp was supplied. + * + * @see #movedFile(IFile, IFile) + */ + public static final long NULL_TIMESTAMP = 0L; + + /** + * Adds the current state of the given file to the local history. + * Does nothing if the file does not exist in the workspace resource tree, + * or if it exists in the workspace resource tree but not in the local file + * system. + *

                          + * This method is used to capture the state of a file in the workspace + * local history before it is overwritten or deleted. + *

                          + * + * @param file the file to be captured + */ + public void addToLocalHistory(IFile file); + + /** + * Returns whether the given resource and its descendents to the given depth + * are considered to be in sync with the local file system. Returns + * false if the given resource does not exist in the workspace + * resource tree, but exists in the local file system; and conversely. + * + * @param resource the resource of interest + * @param depth the depth (one of IResource.DEPTH_ZERO, + * DEPTH_ONE, or DEPTH_INFINITE) + * @return true if the resource is synchronized, and + * false in all other cases + */ + public boolean isSynchronized(IResource resource, int depth); + + /** + * Computes the timestamp for the given file in the local file system. + * Returns NULL_TIMESTAMP if the timestamp of the file in + * the local file system cannot be determined. The file need not exist in + * the workspace resource tree; however, if the file's project does not + * exist in the workspace resource tree, this method returns + * NULL_TIMESTAMP because the project's local content area + * is indeterminate. + *

                          + * Note that the timestamps used for workspace resource tree file + * synchronization are not necessarily interchangeable with + * java.io.File last modification time.The ones computed by + * computeTimestamp may have a higher resolution in some + * operating environments. + *

                          + * + * @param file the file of interest + * @return the local file system timestamp for the file, or + * NULL_TIMESTAMP if it could not be computed + */ + public long computeTimestamp(IFile file); + + /** + * Returns the timestamp for the given file as recorded in the workspace + * resource tree. Returns NULL_TIMESTAMP if the given file + * does not exist in the workspace resource tree, or if the timestamp is + * not known. + *

                          + * Note that the timestamps used for workspace resource tree file + * synchronization are not necessarily interchangeable with + * java.io.File last modification time.The ones computed by + * computeTimestamp may have a higher resolution in some + * operating environments. + *

                          + * + * @param file the file of interest + * @return the workspace resource tree timestamp for the file, or + * NULL_TIMESTAMP if the file does not exist in the + * workspace resource tree, or if the timestamp is not known + */ + public long getTimestamp(IFile file); + + /** + * Updates the timestamp for the given file in the workspace resource tree. + * The file is the local file system is not affected. Does nothing if the + * given file does not exist in the workspace resource tree. + *

                          + * The given timestamp should be that of the corresponding file in the local + * file system (as computed by computeTimestamp). A discrepancy + * between the timestamp of the file in the local file system and the + * timestamp recorded in the workspace resource tree means that the file is + * out of sync (isSynchronized returns false). + *

                          + *

                          + * This operation should be used after movedFile/Folder/Project + * to correct the workspace resource tree record when file timestamps change + * in the course of a move operation. + *

                          + *

                          + * Note that the timestamps used for workspace resource tree file + * synchronization are not necessarily interchangeable with + * java.io.File last modification time.The ones computed by + * computeTimestamp may have a higher resolution in some + * operating environments. + *

                          + * + * @param file the file of interest + * @param timestamp the local file system timestamp for the file, or + * NULL_TIMESTAMP if unknown + * @see #computeTimestamp(IFile) + */ + public void updateMovedFileTimestamp(IFile file, long timestamp); + + /** + * Declares that the operation has failed for the specified reason. + * This method may be called multiple times to report multiple + * failures. All reasons will be accumulated and taken into consideration + * when deciding the outcome of the hooked operation as a whole. + * + * @param reason the reason the operation (or sub-operation) failed + */ + public void failed(IStatus reason); + + /** + * Declares that the given file has been successfully deleted from the + * local file system, and requests that the corresponding deletion should + * now be made to the workspace resource tree. No action is taken if the + * given file does not exist in the workspace resource tree. + *

                          + * This method clears out any markers, session properties, and persistent + * properties associated with the given file. + *

                          + * + * @param file the file that was just deleted from the local file system + */ + public void deletedFile(IFile file); + + /** + * Declares that the given folder and all its descendents have been + * successfully deleted from the local file system, and requests that the + * corresponding deletion should now be made to the workspace resource tree. + * No action is taken if the given folder does not exist in the workspace + * resource tree. + *

                          + * This method clears out any markers, session properties, and persistent + * properties associated with the given folder or its descendents. + *

                          + * + * @param folder the folder that was just deleted from the local file system + */ + public void deletedFolder(IFolder folder); + + /** + * Declares that the given project's content area in the local file system + * has been successfully dealt with in an appropriate manner, and requests + * that the corresponding deletion should now be made to the workspace + * resource tree. No action is taken if the given project does not exist in + * the workspace resource tree. + *

                          + * This method clears out everything associated with this project and any of + * its descendent resources, including: markers; session properties; + * persistent properties; local history; and project-specific plug-ins + * working data areas. The project's content area is not affected. + *

                          + * + * @param project the project being deleted + */ + public void deletedProject(IProject project); + + /** + * Declares that the given source file has been successfully moved to the + * given destination in the local file system, and requests that the + * corresponding changes should now be made to the workspace resource tree. + * No action is taken if the given source file does not exist in the + * workspace resource tree. + *

                          + * The destination file must not already exist in the workspace resource + * tree. + *

                          + * This operation carries over the file timestamp unchanged. Use + * updateMovedFileTimestamp to update the timestamp + * of the file if its timestamp changed as a direct consequence of the move. + *

                          + * + * @param source the handle of the source file that was moved + * @param destination the handle of where the file moved to + * @see #computeTimestamp(IFile) + */ + public void movedFile(IFile source, IFile destination); + + /** + * Declares that the given source folder and its descendents have been + * successfully moved to the given destination in the local file system, + * and requests that the corresponding changes should now be made to the + * workspace resource tree for the folder and all its descendents. No action + * is taken if the given source folder does not exist in the workspace + * resource tree. + *

                          + * This operation carries over file timestamps unchanged. Use + * updateMovedFileTimestamp to update the timestamp of files + * whose timestamps changed as a direct consequence of the move. + *

                          + * The destination folder must not already exist in the workspace resource + * tree. + *

                          + * + * @param source the handle of the source folder that was moved + * @param destination the handle of where the folder moved to + */ + public void movedFolderSubtree(IFolder source, IFolder destination); + + /** + * Declares that the given source project and its files and folders have + * been successfully relocated in the local file system if required, and + * requests that the rename and/or relocation should now be made to the + * workspace resource tree for the project and all its descendents. No + * action is taken if the given project does not exist in the workspace + * resource tree. + *

                          + * This operation carries over file timestamps unchanged. Use + * updateMovedFileTimestamp to update the timestamp of files whose + * timestamps changed as a direct consequence of the move. + *

                          + * If the project is being renamed, the destination project must not + * already exist in the workspace resource tree. + *

                          + * Local history is not preserved if the project is renamed. It is preserved + * when the project's content area is relocated without renaming the + * project. + *

                          + * + * @param source the handle of the source project that was moved + * @param description the new project description + * @return true if the move succeeded, and false + * otherwise + */ + public boolean movedProjectSubtree(IProject source, IProjectDescription description); + + /** + * Deletes the given file in the standard manner from both the local file + * system and from the workspace resource tree. + *

                          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of file.delete(updateFlags, monitor) because all + * regular API operations that modify resources are off limits. + *

                          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                          + * + * @param file the file to delete + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + */ + public void standardDeleteFile(IFile file, int updateFlags, IProgressMonitor monitor); + + /** + * Deletes the given folder and its descendents in the standard manner from + * both the local file system and from the workspace resource tree. + *

                          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of folder.delete(updateFlags, monitor) because all + * regular API operations that modify resources are off limits. + *

                          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                          + * + * @param folder the folder to delete + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + */ + public void standardDeleteFolder(IFolder folder, int updateFlags, IProgressMonitor monitor); + + /** + * Deletes the given project and its descendents in the standard manner from + * both the local file system and from the workspace resource tree. + *

                          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of project.delete(updateFlags, monitor) because all + * regular API operations that modify resources are off limits. + *

                          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                          + * + * @param project the project to delete + * @param updateFlags bit-wise or of update flag constants as per + * IResource.delete(int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.delete(int,IProgressMonitor) + */ + public void standardDeleteProject(IProject project, int updateFlags, IProgressMonitor monitor); + + /** + * Moves the given file in the standard manner from both the local file + * system and from the workspace resource tree. + *

                          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of source.move(destination.getProjectRelativePath(), + * updateFlags, monitor) because all regular API operations that + * modify resources are off limits. + *

                          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                          + * + * @param source the handle of the source file to move + * @param destination the handle of where the file will move to + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + */ + public void standardMoveFile(IFile source, IFile destination, int updateFlags, IProgressMonitor monitor); + + /** + * Moves the given folder and its descendents in the standard manner from + * both the local file system and from the workspace resource tree. + *

                          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of source.move(destination.getProjectRelativePath(), + * updateFlags, monitor) because all regular API operations that + * modify resources are off limits. + *

                          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                          + * + * @param source the handle of the source folder to move + * @param destination the handle of where the folder will move to + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + */ + public void standardMoveFolder(IFolder source, IFolder destination, int updateFlags, IProgressMonitor monitor); + + /** + * Renames and/or relocates the given project in the standard manner. + *

                          + * Implementations of IMoveDeleteHook must invoke this method + * in lieu of source.move(description, updateFlags, monitor) + * because all regular API operations that modify resources are off limits. + *

                          + * If the operation fails, the reason for the failure is automatically + * collected by an internal call to failed. + *

                          + * + * @param source the handle of the source folder to move + * @param description the new project description + * @param updateFlags bit-wise or of update flag constants as per + * IResource.move(IPath,int,IProgressMonitor) + * @param monitor the progress monitor, or null as per + * IResource.move(IPath,int,IProgressMonitor) + */ + public void standardMoveProject(IProject source, IProjectDescription description, int updateFlags, IProgressMonitor monitor); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java new file mode 100644 index 0000000000..0d0ce91df0 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java @@ -0,0 +1,274 @@ +/******************************************************************************* + * 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 + * Lars Vogel - Bug 473427 + * Sebastian Zarnekow - Bug 519776 + *******************************************************************************/ +package org.eclipse.core.resources.team; + +import java.util.HashSet; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; + +/** + * Default implementation of IResourceRuleFactory. The teamHook extension + * may subclass to provide more specialized scheduling rules for workspace operations that + * they participate in. + * + * @see IResourceRuleFactory + * @since 3.0 + */ +public class ResourceRuleFactory implements IResourceRuleFactory { + private final IWorkspace workspace = ResourcesPlugin.getWorkspace(); + + /** + * Creates a new default resource rule factory. This constructor must only + * be called by subclasses. + */ + protected ResourceRuleFactory() { + super(); + } + + /** + * Default implementation of IResourceRuleFactory#buildRule. + * This default implementation always returns the workspace root. + *

                          + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.resources.IResourceRuleFactory#buildRule() + */ + @Override + public final ISchedulingRule buildRule() { + return workspace.getRoot(); + } + + /** + * Default implementation of IResourceRuleFactory#charsetRule. + * This default implementation always returns the project of the resource + * whose charset setting is being changed, or null if the + * resource is the workspace root. + *

                          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + *

                          + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#charsetRule(IResource) + * @since 3.1 + */ + @Override + public ISchedulingRule charsetRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return null; + return resource.getProject(); + } + + /** + * Default implementation of IResourceRuleFactory#derivedRule. + * This default implementation always returns null. + *

                          + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#derivedRule(IResource) + * @since 3.6 + */ + @Override + public final ISchedulingRule derivedRule(IResource resource) { + return null; + } + + /** + * Default implementation of IResourceRuleFactory#copyRule. + * This default implementation always returns the parent of the destination + * resource. + *

                          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#copyRule(IResource, IResource) + */ + @Override + public ISchedulingRule copyRule(IResource source, IResource destination) { + //source is not modified, destination is created + return parent(destination); + } + + /** + * Default implementation of IResourceRuleFactory#createRule. + * This default implementation always returns the parent of the resource + * being created. + *

                          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#createRule(IResource) + */ + @Override + public ISchedulingRule createRule(IResource resource) { + return parent(resource); + } + + /** + * Default implementation of IResourceRuleFactory#deleteRule. + * This default implementation always returns the parent of the resource + * being deleted. + *

                          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#deleteRule(IResource) + */ + @Override + public ISchedulingRule deleteRule(IResource resource) { + return parent(resource); + } + + private boolean isReadOnly(IResource resource) { + ResourceAttributes attributes = resource.getResourceAttributes(); + return attributes == null ? false : attributes.isReadOnly(); + } + + /** + * Default implementation of IResourceRuleFactory#markerRule. + * This default implementation always returns null. + *

                          + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.resources.IResourceRuleFactory#markerRule(IResource) + */ + @Override + public final ISchedulingRule markerRule(IResource resource) { + return null; + } + + /** + * Default implementation of IResourceRuleFactory#syncInfoRule. + * This default implementation always returns null. + *

                          + * Subclasses may not currently override this method. + * + * @see org.eclipse.core.resources.IResourceRuleFactory#syncInfoRule(IResource) + */ + @Override + public final ISchedulingRule syncInfoRule(IResource resource) { + return null; + } + + /** + * Default implementation of IResourceRuleFactory#modifyRule. + * This default implementation returns the resource being modified, or the + * parent resource if modifying a project description file. + * Note that this must encompass any rule required by the validateSave hook. + *

                          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#modifyRule(IResource) + * @see FileModificationValidator#validateSave(IFile) + * @see IProjectDescription#DESCRIPTION_FILE_NAME + */ + @Override + public ISchedulingRule modifyRule(IResource resource) { + IPath path = resource.getFullPath(); + //modifying the project description may cause linked resources to be created or deleted + if (path.segmentCount() == 2 && path.segment(1).equals(IProjectDescription.DESCRIPTION_FILE_NAME)) + return parent(resource); + return resource; + } + + /** + * Default implementation of IResourceRuleFactory#moveRule. + * This default implementation returns a rule that combines the parent + * of the source resource and the parent of the destination resource. + *

                          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#moveRule(IResource, IResource) + */ + @Override + public ISchedulingRule moveRule(IResource source, IResource destination) { + //move needs the parent of both source and destination + return MultiRule.combine(parent(source), parent(destination)); + } + + /** + * Convenience method to return the parent of the given resource, + * or the resource itself for projects and the workspace root. + * @param resource the resource to compute the parent of + * @return the parent resource for folders and files, and the + * resource itself for projects and the workspace root. + */ + protected final ISchedulingRule parent(IResource resource) { + switch (resource.getType()) { + case IResource.ROOT : + case IResource.PROJECT : + return resource; + default : + return resource.getParent(); + } + } + + /** + * Default implementation of IResourceRuleFactory#refreshRule. + * This default implementation always returns the parent of the resource + * being refreshed. + *

                          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#refreshRule(IResource) + */ + @Override + public ISchedulingRule refreshRule(IResource resource) { + return parent(resource); + } + + /** + * Default implementation of IResourceRuleFactory#validateEditRule. + * This default implementation returns a rule that combines the parents of + * all read-only resources, or null if there are no read-only + * resources. + *

                          + * Subclasses may override this method. The rule provided by an overriding + * method must at least contain the rule from this default implementation. + * + * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) + * @see org.eclipse.core.resources.IResourceRuleFactory#validateEditRule(IResource[]) + */ + @Override + public ISchedulingRule validateEditRule(IResource[] resources) { + if (resources.length == 0) + return null; + //optimize rule for single file + if (resources.length == 1) + return isReadOnly(resources[0]) ? parent(resources[0]) : null; + //need a lock on the parents of all read-only files + HashSet rules = new HashSet<>(); + for (int i = 0; i < resources.length; i++) + if (isReadOnly(resources[i])) + rules.add(parent(resources[i])); + if (rules.isEmpty()) + return null; + if (rules.size() == 1) + return rules.iterator().next(); + ISchedulingRule[] ruleArray = rules.toArray(new ISchedulingRule[rules.size()]); + return new MultiRule(ruleArray); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java new file mode 100644 index 0000000000..4e605a1ce2 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java @@ -0,0 +1,231 @@ +/******************************************************************************* + * 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.team; + +import java.net.URI; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.internal.resources.InternalTeamHook; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +/** + * A general hook class for operations that team providers may be + * interested in participating in. Implementors of the hook should provide + * a concrete subclass, and override any methods they are interested in. + *

                          + * This class is intended to be subclassed by the team component in + * conjunction with the org.eclipse.core.resources.teamHook + * standard extension point. Individual team providers may also subclass this + * class. It is not intended to be subclassed by other clients. The methods + * defined on this class are called from within the implementations of + * workspace API methods and must not be invoked directly by clients. + *

                          + * + * @since 2.1 + */ +public abstract class TeamHook extends InternalTeamHook { + /** + * The default resource scheduling rule factory. This factory can be used for projects + * that the team hook methods do not participate in. + * + * @see #getRuleFactory(IProject) + * @see #setRuleFactory(IProject, IResourceRuleFactory) + * @since 3.0 + */ + protected final IResourceRuleFactory defaultFactory = new ResourceRuleFactory(); + + /** + * Creates a new team hook. Default constructor for use by subclasses and the + * resources plug-in only. + */ + protected TeamHook() { + super(); + } + + /** + * Returns the resource scheduling rule factory that should be used when workspace + * operations are invoked on resources in that project. The workspace will ask the + * team hook this question only once per project, per session. The workspace will + * assume the returned result is valid for the rest of that session, unless the rule + * is changed by calling setRuleFactory. + *

                          + * This method must not return null. If no special rules are required + * by the team hook for the given project, the value of the defaultFactory + * field should be returned. + *

                          + * This default implementation always returns the value of the defaultFactory + * field. Subclasses may override and provide a subclass of ResourceRuleFactory. + * + * @param project the project to return scheduling rules for + * @return the resource scheduling rules for a project + * @see #setRuleFactory(IProject, IResourceRuleFactory) + * @see ResourceRuleFactory + * @since 3.0 + */ + public IResourceRuleFactory getRuleFactory(IProject project) { + return defaultFactory; + } + + /** + * Sets the resource scheduling rule factory to use for resource modifications + * in the given project. This method only needs to be called if the factory has changed + * since the initial call to getRuleFactory for the given project + *

                          + * The supplied factory must not be null. If no special rules are required + * by the team hook for the given project, the value of the defaultFactory + * field should be used. + *

                          + * Note that the new rule factory will only take effect for resource changing + * operations that begin after this method completes. Care should be taken to + * avoid calling this method during the invocation of any resource changing + * operation (in any thread). The best time to change rule factories is during resource + * change notification when the workspace is locked for modification. + * + * @param project the project to change the resource rule factory for + * @param factory the new resource rule factory + * @see #getRuleFactory(IProject) + * @see IResourceRuleFactory + * @since 3.0 + */ + @Override + protected final void setRuleFactory(IProject project, IResourceRuleFactory factory) { + super.setRuleFactory(project, factory); + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of IFile.createLink. + *

                          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                          + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

                          + * + * @param file the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system path where the file should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + */ + public IStatus validateCreateLink(IFile file, int updateFlags, IPath location) { + return Status.OK_STATUS; + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of {@link IFile#createLink(URI, int, IProgressMonitor) } + *

                          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                          + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

                          + * + * @param file the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system URI where the file should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + * @since 3.2 + */ + public IStatus validateCreateLink(IFile file, int updateFlags, URI location) { + //forward to old method to ensure old hooks get a chance to validate in the local case + if (EFS.SCHEME_FILE.equals(location.getScheme())) + return validateCreateLink(file, updateFlags, URIUtil.toPath(location)); + return Status.OK_STATUS; + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of IFolder.createLink. + *

                          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                          + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

                          + * + * @param folder the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system path where the folder should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + */ + public IStatus validateCreateLink(IFolder folder, int updateFlags, IPath location) { + return Status.OK_STATUS; + } + + /** + * Validates whether a particular attempt at link creation is allowed. This gives + * team providers an opportunity to hook into the beginning of the implementation + * of {@link IFolder#createLink(URI, int, IProgressMonitor)} + *

                          + * The implementation of this method runs "below" the resources API and is + * therefore very restricted in what resource API method it can call. The + * list of useable methods includes most resource operations that read but + * do not update the resource tree; resource operations that modify + * resources and trigger deltas must not be called from within the dynamic + * scope of the invocation of this method. + *

                          + * This method should be overridden by subclasses that want to control what + * links are created. The default implementation of this method allows all links + * to be created. + *

                          + * + * @param folder the file to be linked + * @param updateFlags bit-wise or of update flag constants + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param location a file system path where the folder should be linked + * @return a status object with code IStatus.OK + * if linking is allowed, otherwise a status object with severity + * IStatus.ERROR indicating why the creation is not allowed. + * @see org.eclipse.core.resources.IResource#ALLOW_MISSING_LOCAL + * @since 3.2 + */ + public IStatus validateCreateLink(IFolder folder, int updateFlags, URI location) { + //forward to old method to ensure old hooks get a chance to validate in the local case + if (EFS.SCHEME_FILE.equals(location.getScheme())) + return validateCreateLink(folder, updateFlags, URIUtil.toPath(location)); + return Status.OK_STATUS; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html new file mode 100644 index 0000000000..5069b75ab0 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/team/package.html @@ -0,0 +1,19 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the Team component. + +

                          Package Specification

                          +This package specifies the APIs in the Resources plug-in which are intended +to be implemented by the Team component. This provides hooks into the +internal workspace tree mechanism as well as lets Team providers receive pre-notification +for resource API calls such as move and delete. + + + diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java new file mode 100644 index 0000000000..7f30a04808 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/PathVariableResolver.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 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: + * Freescale Semiconductor - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.core.resources.variableresolvers; + +import org.eclipse.core.resources.IResource; + +/** + * An interface that variable providers should implement in order + * to extends the default path variable list used to resolve relative + * locations of linked resources. + * @since 3.6 + */ +public abstract class PathVariableResolver { + + /** + * This method can return a list of possible variables resolved by + * this resolver. + *

                          + * This default implementation always returns null. Subclasses + * should override to provide custom extensions. + *

                          + * + * @param variable + * The current variable name. + * @param resource + * The resource that the variable is being resolved for. + * @return the list of supported variables + */ + public String[] getVariableNames(String variable, IResource resource) { + return null; + } + + /** + * Returns a variable value + * + * @param variable + * The current variable name. + * @param resource + * The resource that the variable is being resolved for. + * @return the variable value. + */ + public abstract String getValue(String variable, IResource resource); +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html new file mode 100644 index 0000000000..6a477adf0e --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.resources/src/org/eclipse/core/resources/variableresolvers/package.html @@ -0,0 +1,21 @@ + + + + + + + Package-level Javadoc + + +Provides APIs intended to be implemented by the path variable providers. + +

                          Package Specification

                          +

                          +This package specifies the APIs in the Resources plug-in that are intended +to be implemented and used by plug-ins using the variableResolvers extension point. +

                          +@since 3.6 +

                          + + + diff --git a/third_party/patches/oxygen/pom.xml b/third_party/patches/oxygen/pom.xml new file mode 100644 index 0000000000..300c12cdf6 --- /dev/null +++ b/third_party/patches/oxygen/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + com.google.cloud.tools.eclipse + trunk + 0.1.0-SNAPSHOT + ../.. + + eclipse-patches-oxygen + 0.1.0-SNAPSHOT + pom + + Patches bundles for Eclipse Oxygen + + + org.eclipse.core.resources + + From 5a1c2193bbdae6cc14e79028ec7294a03abc132f Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Fri, 11 Aug 2017 13:57:17 -0400 Subject: [PATCH 7/9] Bump org.eclipse.core.resources to BREE 1.7 as no 1.6 JRE available on Travis --- .../mars/org.eclipse.core.resources/META-INF/MANIFEST.MF | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF b/third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF index 917e0393bb..22dfe134b5 100644 --- a/third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF +++ b/third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF @@ -1,5 +1,4 @@ Manifest-Version: 1.0 -Comment: Bundle-Version: 3.10.1.v20150725-2910 Bundle-Version: 3.10.1.v20150725-1910 Bundle-Localization: plugin Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true @@ -8,7 +7,8 @@ Require-Bundle: org.eclipse.ant.core;bundle-version="[3.1.0,4.0.0)";resolution:= org.eclipse.core.runtime;bundle-version="[3.7.0,4.0.0)" Bundle-ManifestVersion: 2 Bundle-ActivationPolicy: lazy -Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Comment: bumped EE to 1.7 as no 1.6 JRE available on Travis +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-Vendor: %providerName Export-Package: org.eclipse.core.internal.dtree;x-internal:=true, org.eclipse.core.internal.events;x-internal:=true, From f5410a21665fe109e17f37dbe8dee59a9d19488b Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Tue, 22 Aug 2017 10:01:27 -0400 Subject: [PATCH 8/9] Copy in org.eclipse.core.jobs (unpatched) --- .../mars/org.eclipse.core.jobs/.classpath | 7 + .../mars/org.eclipse.core.jobs/.options | 19 + .../mars/org.eclipse.core.jobs/.project | 34 + .../org.eclipse.core.resources.prefs | 3 + .../.settings/org.eclipse.core.runtime.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 398 ++++ .../.settings/org.eclipse.jdt.ui.prefs | 128 ++ .../META-INF/MANIFEST.MF | 18 + .../mars/org.eclipse.core.jobs/about.html | 28 + .../org.eclipse.core.jobs/build.properties | 20 + .../forceQualifierUpdate.txt | 2 + .../org.eclipse.core.jobs/plugin.properties | 13 + .../mars/org.eclipse.core.jobs/plugin.xml | 15 + .../eclipse/core/internal/jobs/Counter.java | 27 + .../eclipse/core/internal/jobs/Deadlock.java | 46 + .../core/internal/jobs/DeadlockDetector.java | 704 +++++++ .../core/internal/jobs/ImplicitJobs.java | 298 +++ .../core/internal/jobs/InternalJob.java | 554 +++++ .../core/internal/jobs/InternalJobGroup.java | 316 +++ .../core/internal/jobs/InternalWorker.java | 76 + .../core/internal/jobs/JobActivator.java | 76 + .../core/internal/jobs/JobChangeEvent.java | 63 + .../core/internal/jobs/JobListeners.java | 167 ++ .../core/internal/jobs/JobManager.java | 1805 +++++++++++++++++ .../core/internal/jobs/JobMessages.java | 52 + .../core/internal/jobs/JobOSGiUtils.java | 127 ++ .../eclipse/core/internal/jobs/JobQueue.java | 171 ++ .../eclipse/core/internal/jobs/JobStatus.java | 38 + .../core/internal/jobs/LockManager.java | 334 +++ .../eclipse/core/internal/jobs/ObjectMap.java | 311 +++ .../core/internal/jobs/OrderedLock.java | 310 +++ .../org/eclipse/core/internal/jobs/Queue.java | 187 ++ .../eclipse/core/internal/jobs/Semaphore.java | 76 + .../eclipse/core/internal/jobs/ThreadJob.java | 451 ++++ .../eclipse/core/internal/jobs/Worker.java | 85 + .../core/internal/jobs/WorkerPool.java | 253 +++ .../core/internal/jobs/messages.properties | 21 + .../core/runtime/jobs/IJobChangeEvent.java | 58 + .../core/runtime/jobs/IJobChangeListener.java | 87 + .../core/runtime/jobs/IJobFunction.java | 51 + .../core/runtime/jobs/IJobManager.java | 426 ++++ .../eclipse/core/runtime/jobs/IJobStatus.java | 29 + .../org/eclipse/core/runtime/jobs/ILock.java | 104 + .../core/runtime/jobs/ISchedulingRule.java | 69 + .../org/eclipse/core/runtime/jobs/Job.java | 878 ++++++++ .../core/runtime/jobs/JobChangeAdapter.java | 55 + .../eclipse/core/runtime/jobs/JobGroup.java | 265 +++ .../core/runtime/jobs/LockListener.java | 77 + .../eclipse/core/runtime/jobs/MultiRule.java | 184 ++ .../core/runtime/jobs/ProgressProvider.java | 98 + .../eclipse/core/runtime/jobs/package.html | 22 + .../META-INF/MANIFEST.MF | 2 +- third_party/patches/mars/pom.xml | 3 +- .../neon/org.eclipse.core.jobs/.classpath | 7 + .../neon/org.eclipse.core.jobs/.options | 19 + .../neon/org.eclipse.core.jobs/.project | 34 + .../org.eclipse.core.resources.prefs | 3 + .../.settings/org.eclipse.core.runtime.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 403 ++++ .../.settings/org.eclipse.jdt.ui.prefs | 128 ++ .../META-INF/MANIFEST.MF | 18 + .../neon/org.eclipse.core.jobs/about.html | 28 + .../org.eclipse.core.jobs/build.properties | 20 + .../forceQualifierUpdate.txt | 2 + .../org.eclipse.core.jobs/plugin.properties | 13 + .../neon/org.eclipse.core.jobs/plugin.xml | 15 + .../eclipse/core/internal/jobs/Counter.java | 27 + .../eclipse/core/internal/jobs/Deadlock.java | 46 + .../core/internal/jobs/DeadlockDetector.java | 704 +++++++ .../core/internal/jobs/ImplicitJobs.java | 298 +++ .../core/internal/jobs/InternalJob.java | 554 +++++ .../core/internal/jobs/InternalJobGroup.java | 316 +++ .../core/internal/jobs/InternalWorker.java | 76 + .../core/internal/jobs/JobActivator.java | 76 + .../core/internal/jobs/JobChangeEvent.java | 63 + .../core/internal/jobs/JobListeners.java | 160 ++ .../core/internal/jobs/JobManager.java | 1805 +++++++++++++++++ .../core/internal/jobs/JobMessages.java | 52 + .../core/internal/jobs/JobOSGiUtils.java | 127 ++ .../eclipse/core/internal/jobs/JobQueue.java | 171 ++ .../eclipse/core/internal/jobs/JobStatus.java | 38 + .../core/internal/jobs/LockManager.java | 334 +++ .../eclipse/core/internal/jobs/ObjectMap.java | 311 +++ .../core/internal/jobs/OrderedLock.java | 310 +++ .../org/eclipse/core/internal/jobs/Queue.java | 187 ++ .../eclipse/core/internal/jobs/Semaphore.java | 76 + .../eclipse/core/internal/jobs/ThreadJob.java | 451 ++++ .../eclipse/core/internal/jobs/Worker.java | 85 + .../core/internal/jobs/WorkerPool.java | 253 +++ .../core/internal/jobs/messages.properties | 21 + .../core/runtime/jobs/IJobChangeEvent.java | 58 + .../core/runtime/jobs/IJobChangeListener.java | 87 + .../core/runtime/jobs/IJobFunction.java | 55 + .../core/runtime/jobs/IJobManager.java | 426 ++++ .../eclipse/core/runtime/jobs/IJobStatus.java | 29 + .../org/eclipse/core/runtime/jobs/ILock.java | 104 + .../core/runtime/jobs/ISchedulingRule.java | 69 + .../org/eclipse/core/runtime/jobs/Job.java | 944 +++++++++ .../core/runtime/jobs/JobChangeAdapter.java | 55 + .../eclipse/core/runtime/jobs/JobGroup.java | 265 +++ .../core/runtime/jobs/LockListener.java | 77 + .../eclipse/core/runtime/jobs/MultiRule.java | 184 ++ .../core/runtime/jobs/ProgressProvider.java | 98 + .../eclipse/core/runtime/jobs/package.html | 22 + .../META-INF/MANIFEST.MF | 2 +- third_party/patches/neon/pom.xml | 3 +- .../oxygen/org.eclipse.core.jobs/.classpath | 7 + .../oxygen/org.eclipse.core.jobs/.options | 19 + .../oxygen/org.eclipse.core.jobs/.project | 34 + .../org.eclipse.core.resources.prefs | 2 + .../.settings/org.eclipse.core.runtime.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 403 ++++ .../.settings/org.eclipse.jdt.ui.prefs | 128 ++ .../META-INF/MANIFEST.MF | 18 + .../oxygen/org.eclipse.core.jobs/about.html | 28 + .../org.eclipse.core.jobs/build.properties | 20 + .../forceQualifierUpdate.txt | 2 + .../org.eclipse.core.jobs/plugin.properties | 13 + .../oxygen/org.eclipse.core.jobs/plugin.xml | 15 + .../oxygen/org.eclipse.core.jobs/pom.xml | 28 + .../eclipse/core/internal/jobs/Counter.java | 27 + .../eclipse/core/internal/jobs/Deadlock.java | 46 + .../core/internal/jobs/DeadlockDetector.java | 704 +++++++ .../core/internal/jobs/ImplicitJobs.java | 298 +++ .../core/internal/jobs/InternalJob.java | 554 +++++ .../core/internal/jobs/InternalJobGroup.java | 316 +++ .../core/internal/jobs/InternalWorker.java | 76 + .../core/internal/jobs/JobActivator.java | 76 + .../core/internal/jobs/JobChangeEvent.java | 63 + .../core/internal/jobs/JobListeners.java | 131 ++ .../core/internal/jobs/JobManager.java | 1802 ++++++++++++++++ .../core/internal/jobs/JobMessages.java | 54 + .../core/internal/jobs/JobOSGiUtils.java | 127 ++ .../eclipse/core/internal/jobs/JobQueue.java | 171 ++ .../eclipse/core/internal/jobs/JobStatus.java | 38 + .../core/internal/jobs/LockManager.java | 334 +++ .../eclipse/core/internal/jobs/ObjectMap.java | 311 +++ .../core/internal/jobs/OrderedLock.java | 310 +++ .../org/eclipse/core/internal/jobs/Queue.java | 187 ++ .../eclipse/core/internal/jobs/Semaphore.java | 76 + .../eclipse/core/internal/jobs/ThreadJob.java | 467 +++++ .../eclipse/core/internal/jobs/Worker.java | 91 + .../core/internal/jobs/WorkerPool.java | 253 +++ .../core/internal/jobs/messages.properties | 22 + .../core/runtime/jobs/IJobChangeEvent.java | 58 + .../core/runtime/jobs/IJobChangeListener.java | 87 + .../core/runtime/jobs/IJobFunction.java | 55 + .../core/runtime/jobs/IJobManager.java | 426 ++++ .../eclipse/core/runtime/jobs/IJobStatus.java | 29 + .../org/eclipse/core/runtime/jobs/ILock.java | 104 + .../core/runtime/jobs/ISchedulingRule.java | 69 + .../org/eclipse/core/runtime/jobs/Job.java | 938 +++++++++ .../core/runtime/jobs/JobChangeAdapter.java | 55 + .../eclipse/core/runtime/jobs/JobGroup.java | 277 +++ .../core/runtime/jobs/LockListener.java | 77 + .../eclipse/core/runtime/jobs/MultiRule.java | 184 ++ .../core/runtime/jobs/ProgressProvider.java | 98 + .../eclipse/core/runtime/jobs/package.html | 22 + .../META-INF/MANIFEST.MF | 2 +- third_party/patches/oxygen/pom.xml | 3 +- 160 files changed, 29085 insertions(+), 6 deletions(-) create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/.classpath create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/.options create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/.project create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.core.resources.prefs create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.core.runtime.prefs create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/.settings/org.eclipse.jdt.ui.prefs create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/META-INF/MANIFEST.MF create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/about.html create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/build.properties create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/forceQualifierUpdate.txt create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/plugin.properties create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/plugin.xml create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Counter.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Deadlock.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/DeadlockDetector.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ImplicitJobs.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJob.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJobGroup.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalWorker.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.properties create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ISchedulingRule.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobChangeAdapter.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobGroup.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/LockListener.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/MultiRule.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ProgressProvider.java create mode 100644 third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/package.html create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/.classpath create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/.options create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/.project create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/.settings/org.eclipse.core.resources.prefs create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/.settings/org.eclipse.core.runtime.prefs create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/.settings/org.eclipse.jdt.ui.prefs create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/META-INF/MANIFEST.MF create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/about.html create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/build.properties create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/forceQualifierUpdate.txt create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/plugin.properties create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/plugin.xml create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Counter.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Deadlock.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/DeadlockDetector.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ImplicitJobs.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJob.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJobGroup.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalWorker.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.properties create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ISchedulingRule.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobChangeAdapter.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobGroup.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/LockListener.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/MultiRule.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ProgressProvider.java create mode 100644 third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/package.html create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/.classpath create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/.options create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/.project create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.core.resources.prefs create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.core.runtime.prefs create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.jdt.ui.prefs create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/META-INF/MANIFEST.MF create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/about.html create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/build.properties create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/forceQualifierUpdate.txt create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/plugin.properties create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/plugin.xml create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/pom.xml create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Counter.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Deadlock.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/DeadlockDetector.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ImplicitJobs.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJob.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJobGroup.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalWorker.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.properties create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ISchedulingRule.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobChangeAdapter.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobGroup.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/LockListener.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/MultiRule.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ProgressProvider.java create mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/package.html 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= +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 monitorStack = new ArrayList(); + + private final InternalWorker internalWorker; + + public static void debug(String msg) { + StringBuffer msgBuf = new StringBuffer(msg.length() + 40); + if (DEBUG_TIMING) { + //lazy initialize to avoid overhead when not debugging + if (DEBUG_FORMAT == null) + DEBUG_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"); //$NON-NLS-1$ + DEBUG_FORMAT.format(new Date(), msgBuf, new FieldPosition(0)); + msgBuf.append('-'); + } + msgBuf.append('[').append(Thread.currentThread()).append(']').append(msg); + System.out.println(msgBuf.toString()); + } + + /** + * Returns the job manager singleton. For internal use only. + */ + static synchronized JobManager getInstance() { + if (instance == null) + new JobManager(); + return instance; + } + + /** + * For debugging purposes only + */ + private static String printJobName(Job job) { + if (job instanceof ThreadJob) { + Job realJob = ((ThreadJob) job).realJob; + if (realJob != null) + return realJob.getClass().getName(); + return "ThreadJob on rule: " + job.getRule(); //$NON-NLS-1$ + } + return job.getClass().getName(); + } + + /** + * For debugging purposes only + */ + public static String printState(Job job) { + return printState(((InternalJob) job).internalGetState()); + } + + /** + * For debugging purposes only + */ + public static String printState(int state) { + switch (state) { + case Job.NONE : + return "NONE"; //$NON-NLS-1$ + case Job.WAITING : + return "WAITING"; //$NON-NLS-1$ + case Job.SLEEPING : + return "SLEEPING"; //$NON-NLS-1$ + case Job.RUNNING : + return "RUNNING"; //$NON-NLS-1$ + case InternalJob.BLOCKED : + return "BLOCKED"; //$NON-NLS-1$ + case InternalJob.YIELDING : + return "YIELDING"; //$NON-NLS-1$ + case InternalJob.ABOUT_TO_RUN : + return "ABOUT_TO_RUN"; //$NON-NLS-1$ + case InternalJob.ABOUT_TO_SCHEDULE : + return "ABOUT_TO_SCHEDULE";//$NON-NLS-1$ + } + return "UNKNOWN"; //$NON-NLS-1$ + } + + /** + * Note that although this method is not API, clients have historically used + * it to force jobs shutdown in cases where OSGi shutdown does not occur. + * For this reason, this method should be considered near-API and should not + * be changed if at all possible. + */ + public static void shutdown() { + if (instance != null) { + instance.doShutdown(); + instance = null; + } + } + + private JobManager() { + instance = this; + synchronized (lock) { + waiting = new JobQueue(false); + waitingThreadJobs = new JobQueue(false, false); + sleeping = new JobQueue(true); + running = new HashSet(10); + yielding = new HashSet(10); + pool = new WorkerPool(this); + } + pool.setDaemon(JobOSGiUtils.getDefault().useDaemonThreads()); + internalWorker = new InternalWorker(this); + internalWorker.setDaemon(JobOSGiUtils.getDefault().useDaemonThreads()); + internalWorker.start(); + jobListeners.add(jobGroupUpdater); + } + + @Override + public void addJobChangeListener(IJobChangeListener listener) { + jobListeners.add(listener); + } + + @Override + public void beginRule(ISchedulingRule rule, IProgressMonitor monitor) { + validateRule(rule); + implicitJobs.begin(rule, monitorFor(monitor), false); + } + + /** + * Cancels a job + */ + protected boolean cancel(InternalJob job) { + IProgressMonitor monitor = null; + boolean runCanceling = false; + synchronized (lock) { + switch (job.getState()) { + case Job.NONE : + return true; + case Job.RUNNING : + //cannot cancel a job that has already started (as opposed to ABOUT_TO_RUN) + if (job.internalGetState() == Job.RUNNING) { + monitor = job.getProgressMonitor(); + runCanceling = !job.isRunCanceled(); + if (runCanceling) + job.setRunCanceled(true); + break; + } + //signal that the job should be canceled before it gets a chance to run + job.setAboutToRunCanceled(true); + return false; + default : + changeState(job, Job.NONE); + } + } + //call monitor and canceling outside sync block + if (monitor != null) { + if (runCanceling) { + if (!monitor.isCanceled()) + monitor.setCanceled(true); + job.canceling(); + } + return false; + } + //only notify listeners if the job was waiting or sleeping + jobListeners.done((Job) job, Status.CANCEL_STATUS, false); + return true; + } + + @Override + public void cancel(Object family) { + //don't synchronize because cancel calls listeners + for (Iterator it = select(family).iterator(); it.hasNext();) + cancel(it.next()); + } + + void cancel(InternalJobGroup jobGroup) { + cancel(jobGroup, false); + } + + void cancel(InternalJobGroup jobGroup, boolean cancelDueToError) { + Assert.isLegal(jobGroup != null, "jobGroup should not be null"); //$NON-NLS-1$ + synchronized (lock) { + switch (jobGroup.getState()) { + case JobGroup.NONE : + return; + case JobGroup.CANCELING : + if (!cancelDueToError) { + // User cancellation takes precedence over the cancel due to error. + jobGroup.updateCancelingReason(cancelDueToError); + } + return; + default : + jobGroup.cancelAndNotify(cancelDueToError); + } + } + } + + /** + * Atomically updates the state of a job, adding or removing from the + * necessary queues or sets. + */ + private void changeState(InternalJob job, int newState) { + boolean blockedJobs = false; + synchronized (lock) { + int oldJobState; + synchronized (job.jobStateLock) { + job.jobStateLock.notifyAll(); + oldJobState = job.getState(); + int oldState = job.internalGetState(); + switch (oldState) { + case InternalJob.YIELDING : + yielding.remove(job); + case Job.NONE : + case InternalJob.ABOUT_TO_SCHEDULE : + break; + case InternalJob.BLOCKED : + //remove this job from the linked list of blocked jobs + job.remove(); + break; + case Job.WAITING : + try { + waiting.remove(job); + } catch (RuntimeException e) { + Assert.isLegal(false, "Tried to remove a job that wasn't in the queue"); //$NON-NLS-1$ + } + break; + case Job.SLEEPING : + try { + sleeping.remove(job); + } catch (RuntimeException e) { + Assert.isLegal(false, "Tried to remove a job that wasn't in the queue"); //$NON-NLS-1$ + } + break; + case Job.RUNNING : + case InternalJob.ABOUT_TO_RUN : + running.remove(job); + //add any blocked jobs back to the wait queue + InternalJob blocked = job.previous(); + job.remove(); + blockedJobs = blocked != null; + while (blocked != null) { + InternalJob previous = blocked.previous(); + changeState(blocked, Job.WAITING); + blocked = previous; + } + break; + default : + Assert.isLegal(false, "Invalid job state: " + job + ", state: " + oldState); //$NON-NLS-1$ //$NON-NLS-2$ + } + job.internalSetState(newState); + switch (newState) { + case Job.NONE : + job.setStartTime(InternalJob.T_NONE); + job.setWaitQueueStamp(InternalJob.T_NONE); + job.setRunCanceled(false); + case InternalJob.BLOCKED : + break; + case Job.WAITING : + waiting.enqueue(job); + break; + case Job.SLEEPING : + try { + sleeping.enqueue(job); + } catch (RuntimeException e) { + throw new RuntimeException("Error changing from state: " + oldState); //$NON-NLS-1$ + } + break; + case Job.RUNNING : + case InternalJob.ABOUT_TO_RUN : + // These flags must be reset in all cases, including resuming from yield + job.setStartTime(InternalJob.T_NONE); + job.setWaitQueueStamp(InternalJob.T_NONE); + running.add(job); + break; + case InternalJob.YIELDING : + yielding.add(job); + case InternalJob.ABOUT_TO_SCHEDULE : + break; + default : + Assert.isLegal(false, "Invalid job state: " + job + ", state: " + newState); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + InternalJobGroup jobGroup = job.getJobGroup(); + if (jobGroup != null) { + jobGroup.jobStateChanged(job, oldJobState, job.getState()); + } + } + + //notify queue outside sync block + if (blockedJobs) + pool.jobQueued(); + } + + /** + * Returns a new progress monitor for this job, belonging to the given + * progress group. Returns null if it is not a valid time to set the job's group. + */ + protected IProgressMonitor createMonitor(InternalJob job, IProgressMonitor group, int ticks) { + synchronized (lock) { + //group must be set before the job is scheduled + //this includes the ABOUT_TO_SCHEDULE state, during which it is still + //valid to set the progress monitor + if (job.getState() != Job.NONE) + return null; + IProgressMonitor monitor = null; + if (progressProvider != null) + monitor = progressProvider.createMonitor((Job) job, group, ticks); + if (monitor == null) + monitor = new NullProgressMonitor(); + return monitor; + } + } + + /** + * Returns a new progress monitor for this job. Never returns null. + * @GuardedBy("lock") + */ + private IProgressMonitor createMonitor(Job job) { + IProgressMonitor monitor = null; + if (progressProvider != null) + monitor = progressProvider.createMonitor(job); + if (monitor == null) + monitor = new NullProgressMonitor(); + return monitor; + } + + @Override + public IProgressMonitor createProgressGroup() { + if (progressProvider != null) + return progressProvider.createProgressGroup(); + return new NullProgressMonitor(); + } + + @Override + public Job currentJob() { + Thread current = Thread.currentThread(); + if (current instanceof Worker) + return ((Worker) current).currentJob(); + synchronized (lock) { + for (Iterator it = running.iterator(); it.hasNext();) { + Job job = (Job) it.next(); + if (job.getThread() == current) + return job; + } + } + return null; + } + + @Override + public ISchedulingRule currentRule() { + //check thread job first, because actual current job may have null rule + Job currentJob = implicitJobs.getThreadJob(Thread.currentThread()); + if (currentJob != null) + return currentJob.getRule(); + currentJob = currentJob(); + if (currentJob != null) + return currentJob.getRule(); + return null; + } + + /** + * Returns the delay in milliseconds that a job with a given priority can + * tolerate waiting. + */ + private long delayFor(int priority) { + //these values may need to be tweaked based on machine speed + switch (priority) { + case Job.INTERACTIVE : + return 0L; + case Job.SHORT : + return 50L; + case Job.LONG : + return 100L; + case Job.BUILD : + return 500L; + case Job.DECORATE : + return 1000L; + default : + Assert.isTrue(false, "Job has invalid priority: " + priority); //$NON-NLS-1$ + return 0; + } + } + + /** + * Performs the scheduling of a job. Does not perform any notifications. + */ + private void doSchedule(InternalJob job, long delay) { + synchronized (lock) { + //job may have been canceled already + int state = job.internalGetState(); + if (state != InternalJob.ABOUT_TO_SCHEDULE && state != Job.SLEEPING) + return; + //if it's a decoration job with no rule, don't run it right now if the system is busy + if (job.getPriority() == Job.DECORATE && job.getRule() == null) { + long minDelay = running.size() * 100; + delay = Math.max(delay, minDelay); + } + if (delay > 0) { + job.setStartTime(System.currentTimeMillis() + delay); + changeState(job, Job.SLEEPING); + } else { + job.setStartTime(System.currentTimeMillis() + delayFor(job.getPriority())); + job.setWaitQueueStamp(waitQueueCounter.increment()); + changeState(job, Job.WAITING); + } + } + } + + /** + * Shuts down the job manager. Currently running jobs will be told + * to stop, but worker threads may still continue processing. + * (note: This implemented IJobManager.shutdown which was removed + * due to problems caused by premature shutdown) + */ + private void doShutdown() { + Job[] toCancel = null; + synchronized (lock) { + if (!active) + return; + active = false; + //cancel all running jobs + toCancel = running.toArray(new Job[running.size()]); + //discard any jobs that have not yet started running + sleeping.clear(); + waiting.clear(); + } + + // Give running jobs a chance to finish. Wait 0.1 seconds for up to 3 times. + if (toCancel != null && toCancel.length > 0) { + for (int i = 0; i < toCancel.length; i++) { + cancel(toCancel[i]); // cancel jobs outside sync block to avoid deadlock + } + + for (int waitAttempts = 0; waitAttempts < 3; waitAttempts++) { + Thread.yield(); + synchronized (lock) { + if (running.isEmpty()) + break; + } + if (DEBUG_SHUTDOWN) { + JobManager.debug("Shutdown - job wait cycle #" + (waitAttempts + 1)); //$NON-NLS-1$ + Job[] stillRunning = null; + synchronized (lock) { + stillRunning = running.toArray(new Job[running.size()]); + } + if (stillRunning != null) { + for (int j = 0; j < stillRunning.length; j++) { + JobManager.debug("\tJob: " + printJobName(stillRunning[j])); //$NON-NLS-1$ + } + } + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + //ignore + } + Thread.yield(); + } + + synchronized (lock) { // retrieve list of the jobs that are still running + toCancel = running.toArray(new Job[running.size()]); + } + } + internalWorker.cancel(); + if (toCancel != null) { + for (int i = 0; i < toCancel.length; i++) { + String jobName = printJobName(toCancel[i]); + //this doesn't need to be translated because it's just being logged + String msg = "Job found still running after platform shutdown. Jobs should be canceled by the plugin that scheduled them during shutdown: " + jobName; //$NON-NLS-1$ + RuntimeLog.log(new Status(IStatus.WARNING, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, null)); + + // TODO the RuntimeLog.log in its current implementation won't produce a log + // during this stage of shutdown. For now add a standard error output. + // One the logging story is improved, the System.err output below can be removed: + System.err.println(msg); + } + } + synchronized (lock) { + //discard reference to any jobs still running at this point + running.clear(); + } + + pool.shutdown(); + jobListeners.remove(jobGroupUpdater); + } + + /** + * Indicates that a job was running, and has now finished. Note that this method + * can be called under OutOfMemoryError conditions and thus must be paranoid + * about allocating objects. + */ + protected void endJob(InternalJob job, IStatus result, boolean notify) { + long rescheduleDelay = InternalJob.T_NONE; + synchronized (lock) { + //if the job is finishing asynchronously, there is nothing more to do for now + if (result == Job.ASYNC_FINISH) + return; + //if job is not known then it cannot be done + if (job.getState() == Job.NONE) + return; + if (JobManager.DEBUG && notify) + JobManager.debug("Ending job: " + job); //$NON-NLS-1$ + job.setResult(result); + job.setProgressMonitor(null); + job.setThread(null); + rescheduleDelay = job.getStartTime(); + changeState(job, Job.NONE); + } + //notify listeners outside sync block + final boolean reschedule = active && rescheduleDelay > InternalJob.T_NONE && job.shouldSchedule(); + if (notify) + jobListeners.done((Job) job, result, reschedule); + //reschedule the job if requested and we are still active + if (reschedule) + schedule(job, rescheduleDelay, reschedule); + //log result if it is warning or error. When the job belongs to a job group defer the logging + //until the whole group is completed (see JobManager#updateJobGroup). + if (job.getJobGroup() == null && result.matches(IStatus.ERROR | IStatus.WARNING)) + RuntimeLog.log(result); + } + + @Override + public void endRule(ISchedulingRule rule) { + implicitJobs.end(rule, false); + } + + @Override + public Job[] find(Object family) { + List members = select(family); + return members.toArray(new Job[members.size()]); + } + + List find(InternalJobGroup jobGroup) { + Assert.isLegal(jobGroup != null, "jobGroup should not be null"); //$NON-NLS-1$ + synchronized (lock) { + return jobGroup.internalGetActiveJobs(); + } + } + + /** + * Returns a running or blocked job whose scheduling rule conflicts with the + * scheduling rule of the given waiting job. Returns null if there are no + * conflicting jobs. A job can only run if there are no running jobs and no blocked + * jobs whose scheduling rule conflicts with its rule. + */ + protected InternalJob findBlockingJob(InternalJob waitingJob) { + if (waitingJob.getRule() == null) + return null; + synchronized (lock) { + if (running.isEmpty()) + return null; + //check the running jobs + boolean hasBlockedJobs = false; + for (Iterator it = running.iterator(); it.hasNext();) { + InternalJob job = it.next(); + if (waitingJob.isConflicting(job)) + return job; + if (!hasBlockedJobs) + hasBlockedJobs = job.previous() != null; + } + //there are no blocked jobs, so we are done + if (!hasBlockedJobs) + return null; + //check all jobs blocked by running jobs + for (Iterator it = running.iterator(); it.hasNext();) { + InternalJob job = it.next(); + while (true) { + job = job.previous(); + if (job == null) + break; + if (waitingJob.isConflicting(job)) + return job; + } + } + } + return null; + } + + /** + * Returns a job from the given collection whose scheduling rule conflicts + * with the scheduling rule of the given job. Returns null if there are no + * conflicting jobs. + */ + InternalJob findBlockedJob(InternalJob job, Iterator jobs) { + synchronized (lock) { + while (jobs.hasNext()) { + InternalJob waitingJob = (InternalJob) jobs.next(); + if (waitingJob.isConflicting(job)) + return waitingJob; + } + return null; + } + } + + void dequeue(JobQueue queue, InternalJob job) { + synchronized (lock) { + queue.remove(job); + } + } + + void enqueue(JobQueue queue, InternalJob job) { + synchronized (lock) { + queue.enqueue(job); + } + } + + public LockManager getLockManager() { + return lockManager; + } + + /** + * Returns a translated message indicating we are waiting for the given + * number of jobs to complete. + */ + private String getWaitMessage(int jobCount) { + String message = jobCount == 1 ? JobMessages.jobs_waitFamSubOne : JobMessages.jobs_waitFamSub; + return NLS.bind(message, Integer.toString(jobCount)); + } + + /** + * Returns whether the job manager is active (has not been shutdown). + */ + protected boolean isActive() { + return active; + } + + /** + * Returns true if the given job is blocking the execution of a non-system + * job. + */ + protected boolean isBlocking(InternalJob runningJob) { + synchronized (lock) { + // if this job isn't running, it can't be blocking anyone + if (runningJob.getState() != Job.RUNNING) + return false; + // if any job is queued behind this one, it is blocked by it + InternalJob previous = runningJob.previous(); + while (previous != null) { + // ignore jobs of lower priority (higher priority value means lower priority) + if (previous.getPriority() < runningJob.getPriority()) { + if (!previous.isSystem()) + return true; + // implicit jobs should interrupt unless they act on behalf of system jobs + if (previous instanceof ThreadJob && ((ThreadJob) previous).shouldInterrupt()) + return true; + } + previous = previous.previous(); + } + // consider threads waiting on IJobManager#beginRule + for (Iterator i = waitingThreadJobs.iterator(); i.hasNext();) { + ThreadJob waitingJob = (ThreadJob) i.next(); + if (runningJob.isConflicting(waitingJob) && waitingJob.shouldInterrupt()) + return true; + } + // none found + return false; + } + } + + @Override + public boolean isIdle() { + synchronized (lock) { + return running.isEmpty() && waiting.isEmpty(); + } + } + + @Override + public boolean isSuspended() { + synchronized (lock) { + return suspended; + } + } + + protected boolean join(InternalJob job, long timeout, IProgressMonitor monitor) throws InterruptedException { + Assert.isLegal(timeout >= 0, "timeout should not be negative"); //$NON-NLS-1$ + long deadline = timeout == 0 ? 0 : System.currentTimeMillis() + timeout; + + Job currentJob = currentJob(); + if (currentJob != null) { + JobGroup jobGroup = currentJob.getJobGroup(); + if (timeout == 0 && jobGroup != null && jobGroup.getMaxThreads() != 0 && jobGroup == job.getJobGroup()) + throw new IllegalStateException("Joining on a job belonging to the same group is not allowed"); //$NON-NLS-1$ + } + + final IJobChangeListener listener; + final Semaphore barrier; + synchronized (lock) { + int state = job.getState(); + if (state == Job.NONE) + return true; + //don't join a waiting or sleeping job when suspended (deadlock risk) + if (suspended && state != Job.RUNNING) + return true; + //it's an error for a job to join itself + if (state == Job.RUNNING && job.getThread() == Thread.currentThread()) + throw new IllegalStateException("Job attempted to join itself"); //$NON-NLS-1$ + //the semaphore will be released when the job is done + barrier = new Semaphore(null); + listener = new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + barrier.release(); + } + }; + job.addJobChangeListener(listener); + } + + //wait until listener notifies this thread. + try { + boolean canBlock = lockManager.canBlock(); + while (true) { + if (monitor != null && monitor.isCanceled()) + throw new OperationCanceledException(); + long remainingTime = deadline; + if (deadline != 0) { + remainingTime -= System.currentTimeMillis(); + if (remainingTime <= 0) { + return false; + } + } + //notify hook to service pending syncExecs before falling asleep + lockManager.aboutToWait(job.getThread()); + try { + // 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; + if (barrier.acquire(sleepTime)) + break; + } catch (InterruptedException e) { + // if non-UI thread, re-throw the exception + if (canBlock) + throw e; + // if UI thread, loop and keep trying + } + } + } finally { + lockManager.aboutToRelease(); + job.removeJobChangeListener(listener); + } + return true; + } + + @Override + public void join(final Object family, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException { + monitor = monitorFor(monitor); + IJobChangeListener listener = null; + final Set jobs; + int jobCount; + Job blocking = null; + synchronized (lock) { + //don't join a waiting or sleeping job when suspended (deadlock risk) + int states = suspended ? Job.RUNNING : Job.RUNNING | Job.WAITING | Job.SLEEPING; + jobs = Collections.synchronizedSet(new HashSet(select(family, states))); + jobCount = jobs.size(); + if (jobCount > 0) { + //if there is only one blocking job, use it in the blockage callback below + if (jobCount == 1) + blocking = (Job) jobs.iterator().next(); + listener = new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + //don't remove from list if job is being rescheduled + if (!((JobChangeEvent) event).reschedule) + jobs.remove(event.getJob()); + } + + //update the list of jobs if new ones are started during the join + @Override + public void running(IJobChangeEvent event) { + Job job = event.getJob(); + if (family == null || job.belongsTo(family)) + jobs.add(job); + } + + //update the list of jobs if new ones are scheduled during the join + @Override + public void scheduled(IJobChangeEvent event) { + //don't add to list if job is being rescheduled + if (((JobChangeEvent) event).reschedule) + return; + //if job manager is suspended we only wait for running jobs + if (isSuspended()) + return; + Job job = event.getJob(); + if (family == null || job.belongsTo(family)) + jobs.add(job); + } + }; + addJobChangeListener(listener); + } + } + if (jobCount == 0) { + //use up the monitor outside synchronized block because monitors call untrusted code + monitor.beginTask(JobMessages.jobs_blocked0, 1); + monitor.done(); + return; + } + //spin until all jobs are completed + try { + monitor.beginTask(JobMessages.jobs_blocked0, jobCount); + monitor.subTask(getWaitMessage(jobCount)); + reportBlocked(monitor, blocking); + int jobsLeft; + int reportedWorkDone = 0; + while ((jobsLeft = jobs.size()) > 0) { + //don't let there be negative work done if new jobs have + //been added since the join began + int actualWorkDone = Math.max(0, jobCount - jobsLeft); + if (reportedWorkDone < actualWorkDone) { + monitor.worked(actualWorkDone - reportedWorkDone); + reportedWorkDone = actualWorkDone; + monitor.subTask(getWaitMessage(jobsLeft)); + } + if (Thread.interrupted()) + throw new InterruptedException(); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + //notify hook to service pending syncExecs before falling asleep + lockManager.aboutToWait(null); + Thread.sleep(100); + } + } finally { + lockManager.aboutToRelease(); + removeJobChangeListener(listener); + reportUnblocked(monitor); + monitor.done(); + } + } + + boolean join(InternalJobGroup jobGroup, long timeout, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException { + Assert.isLegal(jobGroup != null, "jobGroup should not be null"); //$NON-NLS-1$ + Assert.isLegal(timeout >= 0, "timeout should not be negative"); //$NON-NLS-1$ + long deadline = timeout == 0 ? 0 : System.currentTimeMillis() + timeout; + int jobCount; + synchronized (lock) { + jobCount = jobGroup.getActiveJobsCount(); + } + + SubMonitor subMonitor = SubMonitor.convert(monitor, JobMessages.jobs_blocked0, jobCount); + try { + int jobsLeft; + while (true) { + if (subMonitor.isCanceled()) + throw new OperationCanceledException(); + long remainingTime = deadline; + if (deadline != 0) { + remainingTime -= System.currentTimeMillis(); + if (remainingTime <= 0) { + return false; + } + } + synchronized (lock) { + if ((suspended && jobGroup.getRunningJobsCount() == 0)) + break; + } + if (jobGroup.doJoin(remainingTime)) + break; + synchronized (lock) { + jobsLeft = jobGroup.getActiveJobsCount(); + } + if (jobsLeft < jobCount) { + subMonitor.worked(jobCount - jobsLeft); + } else { + jobCount = jobsLeft; + subMonitor.setWorkRemaining(jobCount); + } + subMonitor.subTask(getWaitMessage(jobsLeft)); + } + } finally { + if (monitor != null) { + monitor.done(); + } + } + return true; + } + + /** + * Returns a non-null progress monitor instance. If the monitor is null, + * returns the default monitor supplied by the progress provider, or a + * NullProgressMonitor if no default monitor is available. + */ + private IProgressMonitor monitorFor(IProgressMonitor monitor) { + if (monitor == null || (monitor instanceof NullProgressMonitor)) { + if (progressProvider != null) { + try { + monitor = progressProvider.getDefaultMonitor(); + } catch (Exception e) { + String msg = NLS.bind(JobMessages.meta_pluginProblems, JobManager.PI_JOBS); + RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, e)); + } + } + } + + if (monitor == null) + return new NullProgressMonitor(); + return monitor; + } + + @Override + public ILock newLock() { + return lockManager.newLock(); + } + + /** + * Removes and returns the first waiting job in the queue which is ready to run. + * Returns null if there are no items waiting in the queue. If an item is + * removed from the queue, it is moved to the running jobs list. + */ + private Job nextJob() { + synchronized (lock) { + // do nothing if the job manager is suspended + if (suspended) + return null; + // tickle the sleep queue to see if anyone wakes up + long now = System.currentTimeMillis(); + InternalJob job = sleeping.peek(); + while (job != null && job.getStartTime() < now) { + job.setStartTime(now + delayFor(job.getPriority())); + job.setWaitQueueStamp(waitQueueCounter.increment()); + changeState(job, Job.WAITING); + job = sleeping.peek(); + } + InternalJobGroup jobGroup = null; + // process the wait queue until we find a job whose rules are satisfied. + job = waiting.peek(); + while (job != null) { + InternalJob blocker = findBlockingJob(job); + jobGroup = job.getJobGroup(); + // previous() method returns the next job in the queue. + InternalJob nextWaitingJob = job.previous(); + if (blocker != null) { + // queue this job after the job that's blocking it + changeState(job, InternalJob.BLOCKED); + // assert job does not already belong to some other data structure + Assert.isTrue(job.next() == null); + Assert.isTrue(job.previous() == null); + blocker.addLast(job); + + } else if (jobGroup == null || jobGroup.getMaxThreads() == 0 || (jobGroup.getState() != JobGroup.CANCELING && jobGroup.getRunningJobsCount() < jobGroup.getMaxThreads())) { + break; + } + // skip this job as either this job is blocked on another job or + // the maximum number of jobs from the same group are already running. + job = nextWaitingJob == waiting.dummy ? null : nextWaitingJob; + } + // the job to run must be in the running list before we exit + // the sync block, otherwise two jobs with conflicting rules could start at once + if (job != null) { + changeState(job, InternalJob.ABOUT_TO_RUN); + if (JobManager.DEBUG) + JobManager.debug("Starting job: " + job); //$NON-NLS-1$ + } + return (Job) job; + } + } + + @Override + public void optionsChanged(DebugOptions options) { + DEBUG = options.getBooleanOption(OPTION_DEBUG_JOBS, false); + DEBUG_BEGIN_END = options.getBooleanOption(OPTION_DEBUG_BEGIN_END, false); + DEBUG_YIELDING = options.getBooleanOption(OPTION_DEBUG_YIELDING, false); + DEBUG_YIELDING_DETAILED = options.getBooleanOption(OPTION_DEBUG_YIELDING_DETAILED, false); + DEBUG_DEADLOCK = options.getBooleanOption(OPTION_DEADLOCK_ERROR, false); + DEBUG_LOCKS = options.getBooleanOption(OPTION_LOCKS, false); + DEBUG_TIMING = options.getBooleanOption(OPTION_DEBUG_JOBS_TIMING, false); + DEBUG_SHUTDOWN = options.getBooleanOption(OPTION_SHUTDOWN, false); + } + + @Override + public void removeJobChangeListener(IJobChangeListener listener) { + jobListeners.remove(listener); + } + + /** + * Report to the progress monitor that this thread is blocked, supplying + * an information message, and if possible the job that is causing the blockage. + * Important: An invocation of this method MUST be followed eventually be + * an invocation of reportUnblocked. + * @param monitor The monitor to report blocking to + * @param blockingJob The job that is blocking this thread, or null + * @see #reportUnblocked + */ + final void reportBlocked(IProgressMonitor monitor, InternalJob blockingJob) { + if (!(monitor instanceof IProgressMonitorWithBlocking)) + return; + IStatus reason; + if (blockingJob == null || blockingJob instanceof ThreadJob || blockingJob.isSystem()) { + reason = new Status(IStatus.INFO, JobManager.PI_JOBS, 1, JobMessages.jobs_blocked0, null); + } else { + String msg = NLS.bind(JobMessages.jobs_blocked1, blockingJob.getName()); + reason = new JobStatus(IStatus.INFO, (Job) blockingJob, msg); + } + ((IProgressMonitorWithBlocking) monitor).setBlocked(reason); + } + + /** + * Reports that this thread was blocked, but is no longer blocked and is able + * to proceed. + * @param monitor The monitor to report unblocking to. + * @see #reportBlocked + */ + final void reportUnblocked(IProgressMonitor monitor) { + if (monitor instanceof IProgressMonitorWithBlocking) + ((IProgressMonitorWithBlocking) monitor).clearBlocked(); + } + + @Override + public final void resume() { + synchronized (lock) { + suspended = false; + //poke the job pool + pool.jobQueued(); + } + } + + @Deprecated + @Override + public final void resume(ISchedulingRule rule) { + implicitJobs.resume(rule); + } + + /** + * Attempts to immediately start a given job. Returns null if the job was + * successfully started, and the blocking job if it could not be started immediately + * due to a currently running job with a conflicting rule. Listeners will never + * be notified of jobs that are run in this way. + */ + protected InternalJob runNow(ThreadJob job, boolean releaseWaiting) { + if (releaseWaiting) { + synchronized (implicitJobs) { + synchronized (lock) { + return doRunNow(job, releaseWaiting); + } + } + } + synchronized (lock) { + return doRunNow(job, releaseWaiting); + } + } + + private InternalJob doRunNow(ThreadJob job, boolean releaseWaiting) { + InternalJob blocking = findBlockingJob(job); + //cannot start if there is a conflicting job + if (blocking == null) { + changeState(job, Job.RUNNING); + ((InternalJob) job).setProgressMonitor(new NullProgressMonitor()); + job.run(null); + if (releaseWaiting) { + // atomically release waiting + implicitJobs.removeWaiting(job); + } + } + return blocking; + } + + protected void schedule(InternalJob job, long delay, boolean reschedule) { + if (!active) + throw new IllegalStateException("Job manager has been shut down."); //$NON-NLS-1$ + Assert.isNotNull(job, "Job is null"); //$NON-NLS-1$ + Assert.isLegal(delay >= 0, "Scheduling delay is negative"); //$NON-NLS-1$ + synchronized (lock) { + //if the job is already running, set it to be rescheduled when done + if (job.getState() == Job.RUNNING) { + job.setStartTime(delay); + return; + } + //can't schedule a job that is waiting or sleeping + if (job.internalGetState() != Job.NONE) + return; + if (JobManager.DEBUG) + JobManager.debug("Scheduling job: " + job); //$NON-NLS-1$ + //remember that we are about to schedule the job + //to prevent multiple schedule attempts from succeeding (bug 68452) + changeState(job, InternalJob.ABOUT_TO_SCHEDULE); + } + //notify listeners outside sync block + jobListeners.scheduled((Job) job, delay, reschedule); + //schedule the job + doSchedule(job, delay); + //call the pool outside sync block to avoid deadlock + pool.jobQueued(); + } + + /** + * Adds all family members in the list of jobs to the collection + */ + private void select(List members, Object family, InternalJob firstJob, int stateMask) { + if (firstJob == null) + return; + InternalJob job = firstJob; + do { + //note that job state cannot be NONE at this point + if ((family == null || job.belongsTo(family)) && ((job.getState() & stateMask) != 0)) + members.add(job); + job = job.previous(); + } while (job != null && job != firstJob); + } + + /** + * Returns a list of all jobs known to the job manager that belong to the given family. + */ + private List select(Object family) { + return select(family, Job.WAITING | Job.SLEEPING | Job.RUNNING); + } + + /** + * Returns a list of all jobs known to the job manager that belong to the given + * family and are in one of the provided states. + */ + private List select(Object family, int stateMask) { + List members = new ArrayList(); + synchronized (lock) { + if ((stateMask & Job.RUNNING) != 0) { + for (Iterator it = running.iterator(); it.hasNext();) { + select(members, family, it.next(), stateMask); + } + } + if ((stateMask & Job.WAITING) != 0) { + select(members, family, waiting.peek(), stateMask); + for (Iterator it = yielding.iterator(); it.hasNext();) { + select(members, family, it.next(), stateMask); + } + } + if ((stateMask & Job.SLEEPING) != 0) + select(members, family, sleeping.peek(), stateMask); + } + return members; + } + + @Override + public void setLockListener(LockListener listener) { + lockManager.setLockListener(listener); + } + + /** + * Changes a job priority. + */ + protected void setPriority(InternalJob job, int newPriority) { + synchronized (lock) { + int oldPriority = job.getPriority(); + if (oldPriority == newPriority) + return; + job.internalSetPriority(newPriority); + //if the job is waiting to run, re-shuffle the queue + if (job.getState() == Job.WAITING) { + long oldStart = job.getStartTime(); + job.setStartTime(oldStart + (delayFor(newPriority) - delayFor(oldPriority))); + waiting.resort(job); + } + } + } + + @Override + public void setProgressProvider(ProgressProvider provider) { + progressProvider = provider; + } + + public void setRule(InternalJob job, ISchedulingRule rule) { + synchronized (lock) { + //cannot change the rule of a job that is already running + Assert.isLegal(job.getState() == Job.NONE); + validateRule(rule); + job.internalSetRule(rule); + } + } + + /** + * Puts a job to sleep. Returns true if the job was successfully put to sleep. + */ + protected boolean sleep(InternalJob job) { + synchronized (lock) { + switch (job.getState()) { + case Job.RUNNING : + //cannot be paused if it is already running (as opposed to ABOUT_TO_RUN) + if (job.internalGetState() == Job.RUNNING) + return false; + //job hasn't started running yet (aboutToRun listener) + break; + case Job.SLEEPING : + //update the job wake time + job.setStartTime(InternalJob.T_INFINITE); + //change state again to re-shuffle the sleep queue + changeState(job, Job.SLEEPING); + return true; + case Job.NONE : + return true; + case Job.WAITING : + //put the job to sleep + break; + } + job.setStartTime(InternalJob.T_INFINITE); + changeState(job, Job.SLEEPING); + } + jobListeners.sleeping((Job) job); + return true; + } + + @Override + public void sleep(Object family) { + //don't synchronize because sleep calls listeners + for (Iterator it = select(family).iterator(); it.hasNext();) { + sleep(it.next()); + } + } + + /** + * Returns the estimated time in milliseconds before the next job is scheduled + * to wake up. The result may be negative. Returns InternalJob.T_INFINITE if + * there are no sleeping or waiting jobs. + */ + protected long sleepHint() { + synchronized (lock) { + //wait forever if job manager is suspended + if (suspended) + return InternalJob.T_INFINITE; + if (!waiting.isEmpty()) + return 0L; + //return the anticipated time that the next sleeping job will wake + InternalJob next = sleeping.peek(); + if (next == null) + return InternalJob.T_INFINITE; + return next.getStartTime() - System.currentTimeMillis(); + } + } + + /** + * Implementation of {@link Job#yieldRule(IProgressMonitor)} + */ + protected Job yieldRule(InternalJob job, IProgressMonitor monitor) { + Thread currentThread = Thread.currentThread(); + Assert.isLegal(job.getState() == Job.RUNNING, "Cannot yieldRule job that is " + printState(job.internalGetState())); //$NON-NLS-1$ + Assert.isLegal(currentThread == job.getThread(), "Cannot yieldRule from outside job's thread"); //$NON-NLS-1$ + + InternalJob unblocked; + // If job is not a ThreadJob, and it has implicitly started rules, likeThreadJob + // is the corresponding ThreadJob. Similarly, if likeThreadJob is not null, then + // job is not a ThreadJob + ThreadJob likeThreadJob; + synchronized (implicitJobs) { + synchronized (lock) { + // The nested implicit job, if any + likeThreadJob = implicitJobs.getThreadJob(currentThread); + + unblocked = job.previous(); + + // if unblocked is not null, it was a blocked job. It is guaranteed + // that it will be the next job run by the worker threads once this + // lock is released. + if (unblocked == null) { + + if (likeThreadJob != null) { + + // look for any explicit jobs we may be blocking + unblocked = ((InternalJob) likeThreadJob).previous(); + + if (unblocked == null) { + + // look for any implicit (or yielding) jobs we may be blocking. + unblocked = findBlockedJob(likeThreadJob, waitingThreadJobs.iterator()); + } + + } else { + + // look for any implicit (or yielding) jobs we may be blocking. + unblocked = findBlockedJob(job, waitingThreadJobs.iterator()); + } + } + + // optimization: do nothing if we don't unblock any job + if (unblocked == null) + return null; + + // "release" our rule by exiting RUNNING state + changeState(job, InternalJob.YIELDING); + if (DEBUG_YIELDING) + JobManager.debug(job + " will yieldRule to " + unblocked); //$NON-NLS-1$ + + if (likeThreadJob != null && likeThreadJob != job) { + // if there is a corresponding thread job, it needs yield as well + changeState(likeThreadJob, InternalJob.YIELDING); + if (DEBUG_YIELDING) + JobManager.debug(job + " will yieldRule to " + unblocked); //$NON-NLS-1$ + } + + if (likeThreadJob != null) { + // only null-out threads out for non-ThreadJobs + job.setThread(null); + if (likeThreadJob.getRule() != null) { + getLockManager().removeLockThread(currentThread, likeThreadJob.getRule()); + } + } + + if ((job.getRule() != null) && !(job instanceof ThreadJob)) + getLockManager().removeLockThread(currentThread, job.getRule()); + } + } + // To prevent this job from immediately re-grabbing the scheduling rule wait until + // the unblocked job changes state. This unblocked job is guaranteed to be the + // next job of the set of similar conflicting rules to attempt to run. + if (DEBUG_YIELDING_DETAILED) + JobManager.debug(job + " is waiting for " + unblocked + " to transition from WAITING state"); //$NON-NLS-1$ //$NON-NLS-2$ + + waitForUnblocked(unblocked); + + // restart this job, unless we've been restarted already + // This is the same as ThreadJob begin, except that cancelation CAN NOT be supported + // throwing the OperationCanceledException will return execution to the caller. + IProgressMonitor mon = monitorFor(monitor); + ProgressMonitorWrapper nonCanceling = new ProgressMonitorWrapper(mon) { + @Override + public boolean isCanceled() { + // pass-through request + getWrappedProgressMonitor().isCanceled(); + // ignore result + return false; + } + }; + + if (DEBUG_YIELDING) + JobManager.debug(job + " waiting to resume"); //$NON-NLS-1$ + + // this yielding job becomes an implicit job, unless it is one already + if (likeThreadJob == null) { + // Create a Threadjob proxy. This is strictly an internal job, but its not + // preventing from "leaking" out to clients in the form of listener + // notifications, and via IJobManager API usage like find(). + // Set a flag to differentiate it from regular ThreadJobs. + ThreadJob threadJob = new ThreadJob(job.getRule()) { + @Override + boolean isResumingAfterYield() { + return true; + } + }; + threadJob.setRealJob((Job) job); + ThreadJob.joinRun(threadJob, nonCanceling); + // the following state changes are atomic + synchronized (lock) { + // Must end the temporary threadJob to remove from running list + changeState(threadJob, Job.NONE); + changeState(job, Job.RUNNING); + job.setThread(currentThread); + } + } else { + ThreadJob.joinRun(likeThreadJob, nonCanceling); + synchronized (lock) { + changeState(job, Job.RUNNING); + job.setThread(currentThread); + } + } + if (DEBUG_YIELDING) { + // extra assert: make sure no other conflicting jobs are running now + synchronized (lock) { + for (Iterator it = running.iterator(); it.hasNext();) { + InternalJob other = it.next(); + if (other == job) + continue; + Assert.isTrue(!other.isConflicting(job), other + " conflicts and ran simultaneously with " + job); //$NON-NLS-1$ + } + } + JobManager.debug(job + " resumed"); //$NON-NLS-1$ + } + if (unblocked instanceof ThreadJob && ((ThreadJob) unblocked).isResumingAfterYield()) { + // if the unblocked job is a proxy for a yielding job to start, return + // the original job. No need to expose the proxy ThreadJob. + return ((ThreadJob) unblocked).realJob; + } + return (Job) unblocked; + } + + private void waitForUnblocked(InternalJob theJob) { + // wait until theJob leaves WAITING state + boolean interrupted = false; + synchronized (theJob.jobStateLock) { + if (theJob instanceof ThreadJob) { + // We can't acquire the implicitJob lock while holding jobStateLock, + // so use isWaiting instead. + while (((ThreadJob) theJob).isWaiting) { + try { + theJob.jobStateLock.wait(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } else { + while (theJob.internalGetState() == Job.WAITING) { + try { + theJob.jobStateLock.wait(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } + } + if (interrupted) + Thread.currentThread().interrupt(); + } + + /** + * Invokes {@link Job#shouldRun()} while guarding against unexpected failures. + */ + private boolean shouldRun(Job job) { + Throwable t; + try { + return job.shouldRun(); + } catch (Exception e) { + t = e; + } catch (LinkageError e) { + t = e; + } catch (AssertionError e) { + t = e; + } + RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "Error invoking shouldRun() method on: " + job, t)); //$NON-NLS-1$ + //if the should is unexpectedly failing it is safer not to run it + return false; + } + + /** + * Returns the next job to be run, or null if no jobs are waiting to run. + * The worker must call endJob when the job is finished running. + */ + protected Job startJob(Worker worker) { + Job job = null; + while (true) { + job = nextJob(); + if (job == null) + return null; + //must perform this outside sync block because it is third party code + boolean shouldRun = shouldRun(job); + //check for listener veto + if (shouldRun) + jobListeners.aboutToRun(job); + //listeners may have canceled or put the job to sleep + boolean endJob = false; + synchronized (lock) { + JobGroup jobGroup = job.getJobGroup(); + if (jobGroup != null && jobGroup.getState() == JobGroup.CANCELING) + shouldRun = false; + InternalJob internal = job; + synchronized (internal.jobStateLock) { + if (internal.internalGetState() == InternalJob.ABOUT_TO_RUN) { + if (shouldRun && !internal.isAboutToRunCanceled()) { + internal.setProgressMonitor(createMonitor(job)); + //change from ABOUT_TO_RUN to RUNNING + internal.setThread(worker); + internal.internalSetState(Job.RUNNING); + internal.jobStateLock.notifyAll(); + break; + } + internal.setAboutToRunCanceled(false); + endJob = true; + //fall through and end the job below + } + } + } + if (endJob) { + //job has been vetoed or canceled, so mark it as done + endJob(job, Status.CANCEL_STATUS, true); + continue; + } + } + jobListeners.running(job); + return job; + + } + + @Override + public final void suspend() { + synchronized (lock) { + suspended = true; + } + } + + @Deprecated + @Override + public final void suspend(ISchedulingRule rule, IProgressMonitor monitor) { + Assert.isNotNull(rule); + implicitJobs.suspend(rule, monitorFor(monitor)); + } + + @Override + public void transferRule(ISchedulingRule rule, Thread destinationThread) { + implicitJobs.transfer(rule, destinationThread); + } + + /** + * Validates that the given scheduling rule obeys the constraints of + * scheduling rules as described in the ISchedulingRule + * javadoc specification. + */ + private void validateRule(ISchedulingRule rule) { + //null rule always valid + if (rule == null) + return; + if (rule instanceof MultiRule) { + ISchedulingRule[] children = ((MultiRule) rule).getChildren(); + for (int i = 0; i < children.length; i++) { + Assert.isLegal(children[i] != rule); + validateRule(children[i]); + } + } + //contains method must be reflexive + Assert.isLegal(rule.contains(rule)); + //contains method must return false when given an unknown rule + Assert.isLegal(!rule.contains(nullRule)); + //isConflicting method must be reflexive + Assert.isLegal(rule.isConflicting(rule)); + //isConflicting method must return false when given an unknown rule + Assert.isLegal(!rule.isConflicting(nullRule)); + } + + protected void wakeUp(InternalJob job, long delay) { + Assert.isLegal(delay >= 0, "Scheduling delay is negative"); //$NON-NLS-1$ + synchronized (lock) { + //cannot wake up if it is not sleeping + if (job.getState() != Job.SLEEPING) + return; + doSchedule(job, delay); + } + //call the pool outside sync block to avoid deadlock + pool.jobQueued(); + + //only notify of wake up if immediate + if (delay == 0) + jobListeners.awake((Job) job); + } + + @Override + public void wakeUp(Object family) { + //don't synchronize because wakeUp calls listeners + for (Iterator it = select(family).iterator(); it.hasNext();) { + wakeUp(it.next(), 0L); + } + } + + void endMonitoring(ThreadJob threadJob) { + synchronized (monitorStack) { + for (int i = monitorStack.size() - 1; i >= 0; i--) { + if (monitorStack.get(i)[0] == threadJob) { + monitorStack.remove(i); + monitorStack.notifyAll(); + break; + } + } + } + } + + void beginMonitoring(ThreadJob threadJob, IProgressMonitor monitor) { + synchronized (monitorStack) { + monitorStack.add(new Object[] {threadJob, monitor}); + monitorStack.notifyAll(); + } + } + + /** + * Listens for the job completion events and checks for the job group cancellation, + * computes and logs the group result. + */ + private class JobGroupUpdater extends JobChangeAdapter { + Object jobManagerLock; + + public JobGroupUpdater(Object jobManagerLock) { + this.jobManagerLock = jobManagerLock; + } + + @Override + public void done(IJobChangeEvent event) { + InternalJob job = event.getJob(); + InternalJobGroup jobGroup = job.getJobGroup(); + if (jobGroup == null) + return; + IStatus jobResult = event.getResult(); + boolean reschedule = ((JobChangeEvent) event).reschedule; + + int jobGroupState; + int activeJobsCount; + int failedJobsCount; + int canceledJobsCount; + int seedJobsRemainingCount; + List jobResults = Collections.emptyList(); + synchronized (jobManagerLock) { + // Collect the required details to check for the group cancellation and completion + // outside the synchronized block. + jobGroupState = jobGroup.getState(); + activeJobsCount = jobGroup.getActiveJobsCount(); + failedJobsCount = jobGroup.getFailedJobsCount(); + canceledJobsCount = jobGroup.getCanceledJobsCount(); + seedJobsRemainingCount = jobGroup.getseedJobsRemainingCount(); + if (activeJobsCount == 0) + jobResults = jobGroup.getCompletedJobResults(); + } + + // Check for the group completion. + if (!reschedule && jobGroupState != JobGroup.NONE && activeJobsCount == 0 && (seedJobsRemainingCount <= 0 || jobGroupState == JobGroup.CANCELING)) { + // Must perform this outside the sync block to avoid a potential deadlock + MultiStatus jobGroupResult = jobGroup.computeGroupResult(jobResults); + Assert.isLegal(jobGroupResult != null, "The group result should not be null"); //$NON-NLS-1$ + boolean isJobGroupCompleted = false; + synchronized (jobManagerLock) { + // If more jobs were added to the group while were computing the result, the job group + // remains in the ACTIVE state and the computed result is discarded to be recomputed later, + // after the new jobs finish. + if (jobGroup.getState() != JobGroup.NONE && jobGroup.getActiveJobsCount() == 0) { + jobGroup.endJobGroup(jobGroupResult); + isJobGroupCompleted = true; + } + } + + // If the job group is completing, add the job group's status to the event + // and log errors and warnings. + if (isJobGroupCompleted) { + ((JobChangeEvent) event).jobGroupResult = jobGroupResult; + if (jobGroupResult.matches(IStatus.ERROR | IStatus.WARNING)) + RuntimeLog.log(jobGroupResult); + } + + return; + } + + if (jobGroupState != JobGroup.CANCELING && jobGroup.shouldCancel(jobResult, failedJobsCount, canceledJobsCount)) + cancel(jobGroup, true); + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java new file mode 100644 index 0000000000..48522c8bae --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java @@ -0,0 +1,52 @@ +/********************************************************************** + * 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 - Initial API and implementation + **********************************************************************/ +package org.eclipse.core.internal.jobs; + +import java.util.Date; +import org.eclipse.osgi.util.NLS; + +/** + * Job plugin message catalog + */ +public class JobMessages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.core.internal.jobs.messages"; //$NON-NLS-1$ + + // Job Manager and Locks + public static String jobs_blocked0; + public static String jobs_blocked1; + public static String jobs_internalError; + public static String jobs_waitFamSub; + public static String jobs_waitFamSubOne; + // metadata + public static String meta_pluginProblems; + + static { + // load message values from bundle file + reloadMessages(); + } + + public static void reloadMessages() { + NLS.initializeMessages(BUNDLE_NAME, JobMessages.class); + } + + /** + * Print a debug message to the console. + * Pre-pend the message with the current date and the name of the current thread. + */ + public static void message(String message) { + StringBuffer buffer = new StringBuffer(); + buffer.append(new Date(System.currentTimeMillis())); + buffer.append(" - ["); //$NON-NLS-1$ + buffer.append(Thread.currentThread().getName()); + buffer.append("] "); //$NON-NLS-1$ + buffer.append(message); + System.out.println(buffer.toString()); + } +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java new file mode 100644 index 0000000000..813aafd3b6 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * 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.jobs; + +import java.util.Hashtable; +import org.eclipse.core.runtime.jobs.IJobManager; +import org.eclipse.osgi.service.debug.DebugOptions; +import org.eclipse.osgi.service.debug.DebugOptionsListener; +import org.osgi.framework.*; +import org.osgi.service.packageadmin.PackageAdmin; +import org.osgi.util.tracker.ServiceTracker; + +/** + * The class contains a set of helper methods for the runtime Jobs plugin. + * The following utility methods are supplied: + * - provides access to debug options + * - provides some bundle discovery functionality + * + * The closeServices() method should be called before the plugin is stopped. + * + * @since org.eclipse.core.jobs 3.2 + */ +class JobOSGiUtils { + private ServiceRegistration debugRegistration = null; + private ServiceTracker bundleTracker = null; + + private static final JobOSGiUtils singleton = new JobOSGiUtils(); + + /** + * Accessor for the singleton instance + * @return The JobOSGiUtils instance + */ + public static JobOSGiUtils getDefault() { + return singleton; + } + + /** + * Private constructor to block instance creation. + */ + private JobOSGiUtils() { + super(); + } + + @SuppressWarnings("unchecked") + void openServices() { + BundleContext context = JobActivator.getContext(); + if (context == null) { + if (JobManager.DEBUG) + JobMessages.message("JobsOSGiUtils called before plugin started"); //$NON-NLS-1$ + return; + } + + // register debug options listener + Hashtable properties = new Hashtable(2); + properties.put(DebugOptions.LISTENER_SYMBOLICNAME, JobManager.PI_JOBS); + debugRegistration = context.registerService(DebugOptionsListener.class, JobManager.getInstance(), properties); + + bundleTracker = new ServiceTracker(context, PackageAdmin.class.getName(), null); + bundleTracker.open(); + } + + void closeServices() { + if (debugRegistration != null) { + debugRegistration.unregister(); + debugRegistration = null; + } + if (bundleTracker != null) { + bundleTracker.close(); + bundleTracker = null; + } + } + + /** + * Returns the bundle id of the bundle that contains the provided object, or + * null if the bundle could not be determined. + */ + public String getBundleId(Object object) { + if (bundleTracker == null) { + if (JobManager.DEBUG) + JobMessages.message("Bundle tracker is not set"); //$NON-NLS-1$ + return null; + } + PackageAdmin packageAdmin = (PackageAdmin) bundleTracker.getService(); + if (object == null) + return null; + if (packageAdmin == null) + return null; + Bundle source = packageAdmin.getBundle(object.getClass()); + if (source != null && source.getSymbolicName() != null) + return source.getSymbolicName(); + return null; + } + + /** + * Calculates whether the job plugin should set worker threads to be daemon + * threads. When workers are daemon threads, the job plugin does not need + * to be explicitly shut down because the VM can exit while workers are still + * alive. + * @return true if all worker threads should be daemon threads, + * and false otherwise. + */ + boolean useDaemonThreads() { + BundleContext context = JobActivator.getContext(); + if (context == null) { + //we are running stand-alone, so consult global system property + String value = System.getProperty(IJobManager.PROP_USE_DAEMON_THREADS); + //default to use daemon threads if property is absent + if (value == null) + return true; + return "true".equalsIgnoreCase(value); //$NON-NLS-1$ + } + //only use daemon threads if the property is defined + final String value = context.getProperty(IJobManager.PROP_USE_DAEMON_THREADS); + //if value is absent, don't use daemon threads to maintain legacy behaviour + if (value == null) + return false; + return "true".equalsIgnoreCase(value); //$NON-NLS-1$ + } +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java new file mode 100644 index 0000000000..356c643698 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * 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.Iterator; +import org.eclipse.core.runtime.*; + +/** + * A linked list based priority queue. + */ +public final class JobQueue { + /** + * The dummy entry sits between the head and the tail of the queue. + * dummy.previous() is the head, and dummy.next() is the tail. + */ + protected final InternalJob dummy; + + /** + * If true, conflicting jobs will be allowed to overtake others in the + * queue that have lower priority. If false, higher priority jumps can only + * move up the queue by overtaking jobs that they don't conflict with. + */ + private final boolean allowConflictOvertaking; + + private final boolean allowPriorityOvertaking; + + /** + * Create a new job queue. + */ + public JobQueue(boolean allowConflictOvertaking) { + this(allowConflictOvertaking, true); + } + + /** + * Create a new job queue. + */ + public JobQueue(boolean allowConflictOvertaking, boolean allowPriorityOvertaking) { + this.allowPriorityOvertaking = allowPriorityOvertaking; + //compareTo on dummy is never called + dummy = new InternalJob("Queue-Head") {//$NON-NLS-1$ + @Override + public IStatus run(IProgressMonitor m) { + return Status.OK_STATUS; + } + }; + dummy.setNext(dummy); + dummy.setPrevious(dummy); + this.allowConflictOvertaking = allowConflictOvertaking; + } + + /** + * remove all elements + */ + public void clear() { + dummy.setNext(dummy); + dummy.setPrevious(dummy); + } + + /** + * Return and remove the element with highest priority, or null if empty. + */ + public InternalJob dequeue() { + InternalJob toRemove = dummy.previous(); + if (toRemove == dummy) + return null; + return toRemove.remove(); + } + + /** + * Adds an item to the queue + */ + public void enqueue(InternalJob newEntry) { + //assert new entry is does not already belong to some other data structure + Assert.isTrue(newEntry.next() == null); + Assert.isTrue(newEntry.previous() == null); + InternalJob tail = dummy.next(); + //overtake lower priority jobs. Only overtake conflicting jobs if allowed to + while (canOvertake(newEntry, tail)) + tail = tail.next(); + //new entry is smaller than tail + final InternalJob tailPrevious = tail.previous(); + newEntry.setNext(tail); + newEntry.setPrevious(tailPrevious); + tailPrevious.setNext(newEntry); + tail.setPrevious(newEntry); + } + + /** + * Returns whether the new entry to overtake the existing queue entry. + * @param newEntry The entry to be added to the queue + * @param queueEntry The existing queue entry + */ + private boolean canOvertake(InternalJob newEntry, InternalJob queueEntry) { + //can never go past the end of the queue + if (queueEntry == dummy) + return false; + //if the new entry was already in the wait queue, ensure it is re-inserted in correct position (bug 211799) + if (newEntry.getWaitQueueStamp() > 0 && newEntry.getWaitQueueStamp() < queueEntry.getWaitQueueStamp()) + return true; + //if the new entry has lower priority, there is no need to overtake the existing entry + if (allowPriorityOvertaking && queueEntry.compareTo(newEntry) >= 0) + return false; + //the new entry has higher priority, but only overtake the existing entry if the queue allows it + return allowConflictOvertaking || !newEntry.isConflicting(queueEntry); + } + + /** + * Removes the given element from the queue. + */ + public void remove(InternalJob toRemove) { + toRemove.remove(); + //previous of toRemove might now bubble up + } + + /** + * The given object has changed priority. Reshuffle the heap until it is + * valid. + */ + public void resort(InternalJob entry) { + remove(entry); + enqueue(entry); + } + + /** + * Returns true if the queue is empty, and false otherwise. + */ + public boolean isEmpty() { + return dummy.next() == dummy; + } + + /** + * Return greatest element without removing it, or null if empty + */ + public InternalJob peek() { + return dummy.previous() == dummy ? null : dummy.previous(); + } + + public Iterator iterator() { + return new Iterator() { + InternalJob pointer = dummy; + + @Override + public boolean hasNext() { + if (pointer.previous() == dummy) + pointer = null; + else + pointer = pointer.previous(); + return pointer != null; + } + + @Override + public Object next() { + return pointer; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java new file mode 100644 index 0000000000..af4f97e62d --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * 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.internal.jobs; + +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.IJobStatus; +import org.eclipse.core.runtime.jobs.Job; + +/** + * Standard implementation of the IJobStatus interface. + */ +public class JobStatus extends Status implements IJobStatus { + private Job job; + + /** + * Creates a new job status with no interesting error code or exception. + * @param severity + * @param job + * @param message + */ + public JobStatus(int severity, Job job, String message) { + super(severity, JobManager.PI_JOBS, 1, message, null); + this.job = job; + } + + @Override + public Job getJob() { + return job; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java new file mode 100644 index 0000000000..8af48dbaa2 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java @@ -0,0 +1,334 @@ +/******************************************************************************* + * 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 java.util.HashMap; +import java.util.Stack; +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.LockListener; + +/** + * Stores the only reference to the graph that contains all the known + * relationships between locks, rules, and the threads that own them. + * Synchronizes all access to the graph on the only instance that exists in this class. + * + * Also stores the state of suspended locks so that they can be re-acquired with + * the proper lock depth. + */ +public class LockManager { + /** + * This class captures the state of suspended locks. + * Locks are suspended if deadlock is detected. + */ + private static class LockState { + private int depth; + private OrderedLock lock; + + /** + * Suspends ownership of the given lock, and returns the saved state. + */ + protected static LockState suspend(OrderedLock lock) { + LockState state = new LockState(); + state.lock = lock; + state.depth = lock.forceRelease(); + return state; + } + + /** + * Re-acquires a suspended lock and reverts to the correct lock depth. + */ + public void resume() { + //spin until the lock is successfully acquired + //NOTE: spinning here allows the UI thread to service pending syncExecs + //if the UI thread is waiting to acquire a lock. + while (true) { + try { + if (lock.acquire(Long.MAX_VALUE)) + break; + } catch (InterruptedException e) { + //ignore and loop + } + } + lock.setDepth(depth); + } + } + + //the lock listener for this lock manager + protected LockListener lockListener; + /* + * The internal data structure that stores all the relationships + * between the locks (or rules) and the threads that own them. + */ + private DeadlockDetector locks = new DeadlockDetector(); + /* + * Stores thread - stack pairs where every entry in the stack is an array + * of locks that were suspended while the thread was acquiring more locks + * (a stack is needed because when a thread tries to re-acquire suspended locks, + * it can cause deadlock, and some locks it owns can be suspended again) + */ + private HashMap> suspendedLocks = new HashMap>(); + + public LockManager() { + super(); + } + + public void aboutToRelease() { + if (lockListener == null) + return; + try { + lockListener.aboutToRelease(); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + } + + public boolean canBlock() { + if (lockListener == null) + return true; + try { + return lockListener.canBlock(); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + return false; + } + + public boolean aboutToWait(Thread lockOwner) { + if (lockListener == null) + return false; + try { + return lockListener.aboutToWait(lockOwner); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + return false; + } + + /** + * This thread has just acquired a lock. Update graph. + */ + void addLockThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockAcquired(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just been refused a lock. Update graph and check for deadlock. + */ + void addLockWaitThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + Deadlock found = null; + synchronized (tempLocks) { + try { + found = tempLocks.lockWaitStart(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + if (found == null) + return; + // if deadlock was detected, the found variable will contain all the information about it, + // including which locks to suspend for which thread to resolve the deadlock. + ISchedulingRule[] toSuspend = found.getLocks(); + LockState[] suspended = new LockState[toSuspend.length]; + for (int i = 0; i < toSuspend.length; i++) + suspended[i] = LockState.suspend((OrderedLock) toSuspend[i]); + synchronized (suspendedLocks) { + Stack prevLocks = suspendedLocks.get(found.getCandidate()); + if (prevLocks == null) + prevLocks = new Stack(); + prevLocks.push(suspended); + suspendedLocks.put(found.getCandidate(), prevLocks); + } + } catch (Exception e) { + handleInternalError(e); + } + } + + private Exception createDebugException(DeadlockDetector tempLocks, Exception rootException) { + String debugString = null; + try { + debugString = tempLocks.toDebugString(); + } catch (Exception e) { + //ignore failure to create the debug string + } + return new Exception(debugString, rootException); + } + + /** + * Handles exceptions that occur while calling third party code from within the + * LockManager. This is essentially an in-lined version of Platform.run(ISafeRunnable) + */ + private static void handleException(Throwable e) { + IStatus status; + if (e instanceof CoreException) { + //logged message should not be translated + status = new MultiStatus(JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "LockManager.handleException", e); //$NON-NLS-1$ + ((MultiStatus) status).merge(((CoreException) e).getStatus()); + } else { + status = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "LockManager.handleException", e); //$NON-NLS-1$ + } + RuntimeLog.log(status); + } + + /** + * There was an internal error in the deadlock detection code. Shut the entire + * thing down to prevent further errors. Recovery is too complex as it + * requires freezing all threads and inferring the present lock state. + */ + private void handleInternalError(Throwable t) { + try { + handleException(t); + } catch (Exception e) { + //ignore failure to log + } + //discard the deadlock detector for good + locks = null; + } + + /** + * Returns true IFF the underlying graph is empty. + * For debugging purposes only. + */ + public boolean isEmpty() { + return locks.isEmpty(); + } + + /** + * Returns true IFF this thread either owns, or is waiting for, any locks or rules. + */ + public boolean isLockOwner() { + //all job threads have to be treated as lock owners because UI thread + //may try to join a job + Thread current = Thread.currentThread(); + if (current instanceof Worker) + return true; + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return false; + synchronized (tempLocks) { + return tempLocks.contains(Thread.currentThread()); + } + } + + /** + * Creates and returns a new lock. + */ + public synchronized OrderedLock newLock() { + return new OrderedLock(this); + } + + /** + * Releases all the acquires that were called on the given rule. Needs to be called only once. + */ + void removeLockCompletely(Thread thread, ISchedulingRule rule) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockReleasedCompletely(thread, rule); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just released a lock. Update graph. + */ + void removeLockThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockReleased(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just stopped waiting for a lock. Update graph. + * If the thread has already been granted the lock (or wasn't waiting + * for the lock) then the graph remains unchanged. + */ + void removeLockWaitThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockWaitStop(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * Resumes all the locks that were suspended while this thread was waiting to acquire another lock. + */ + void resumeSuspendedLocks(Thread owner) { + LockState[] toResume; + synchronized (suspendedLocks) { + Stack prevLocks = suspendedLocks.get(owner); + if (prevLocks == null) + return; + toResume = (LockState[]) prevLocks.pop(); + if (prevLocks.empty()) + suspendedLocks.remove(owner); + } + for (int i = 0; i < toResume.length; i++) + toResume[i].resume(); + } + + public void setLockListener(LockListener listener) { + this.lockListener = listener; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java new file mode 100644 index 0000000000..6cd59f7584 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java @@ -0,0 +1,311 @@ +/******************************************************************************* + * 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.internal.jobs; + +import java.util.*; + +/** + * A specialized map implementation that is optimized for a small set of object + * keys. + * + * Implemented as a single array that alternates keys and values. + * + * Note: This class is copied from org.eclipse.core.resources + */ +public class ObjectMap implements Map { + // 8 attribute keys, 8 attribute values + protected static final int DEFAULT_SIZE = 16; + protected static final int GROW_SIZE = 10; + protected int count = 0; + protected Object[] elements = null; + + /** + * Creates a new object map. + * + * @param initialCapacity + * The initial number of elements that will fit in the map. + */ + public ObjectMap(int initialCapacity) { + if (initialCapacity > 0) + elements = new Object[Math.max(initialCapacity * 2, 0)]; + } + + /** + * Creates a new object map of the same size as the given map and populate + * it with the key/attribute pairs found in the map. + * + * @param map + * The entries in the given map will be added to the new map. + */ + public ObjectMap(Map map) { + this(map.size()); + putAll(map); + } + + /** + * @see Map#clear() + */ + @Override + public void clear() { + elements = null; + count = 0; + } + + /** + * @see java.lang.Object#clone() + */ + @Override + public Object clone() { + return new ObjectMap(this); + } + + /** + * @see Map#containsKey(java.lang.Object) + */ + @Override + public boolean containsKey(Object key) { + if (elements == null || count == 0) + return false; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return true; + return false; + } + + /** + * @see Map#containsValue(java.lang.Object) + */ + @Override + public boolean containsValue(Object value) { + if (elements == null || 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; + } + + /** + * @see Map#entrySet() + * + * Note: 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 count == 0 ? Collections.EMPTY_SET : toHashMap().entrySet(); + } + + /** + * @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; + //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; + } + + /** + * @see Map#get(java.lang.Object) + */ + @Override + public Object get(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return elements[i + 1]; + return null; + } + + /** + * The capacity of the map has been exceeded, grow the array by GROW_SIZE to + * accommodate more entries. + */ + protected void grow() { + Object[] expanded = new Object[elements.length + GROW_SIZE]; + System.arraycopy(elements, 0, expanded, 0, elements.length); + elements = expanded; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 0; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + hash += elements[i].hashCode(); + } + } + return hash; + } + + /** + * @see Map#isEmpty() + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * @see Map#keySet() + * + * Note: 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()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.add(elements[i]); + } + } + return result; + } + + /** + * @see Map#put(java.lang.Object, java.lang.Object) + */ + @Override + public Object put(Object key, Object value) { + if (key == null) + throw new NullPointerException(); + if (value == null) + return remove(key); + + // handle the case where we don't have any attributes yet + if (elements == null) + elements = new Object[DEFAULT_SIZE]; + if (count == 0) { + elements[0] = key; + elements[1] = value; + count++; + return null; + } + + int emptyIndex = -1; + // replace existing value if it exists + for (int i = 0; i < elements.length; i += 2) { + if (elements[i] != null) { + if (elements[i].equals(key)) { + Object oldValue = elements[i + 1]; + elements[i + 1] = value; + return oldValue; + } + } else if (emptyIndex == -1) { + // keep track of the first empty index + emptyIndex = i; + } + } + // this will put the emptyIndex greater than the size but + // that's ok because we will grow first. + if (emptyIndex == -1) + emptyIndex = count * 2; + + // otherwise add it to the list of elements. + // grow if necessary + if (elements.length <= (count * 2)) + grow(); + elements[emptyIndex] = key; + elements[emptyIndex + 1] = value; + count++; + return null; + } + + /** + * @see Map#putAll(java.util.Map) + */ + @Override + public void putAll(Map map) { + for (Iterator i = map.keySet().iterator(); i.hasNext();) { + Object key = i.next(); + Object value = map.get(key); + put(key, value); + } + } + + /** + * @see Map#remove(java.lang.Object) + */ + @Override + public Object remove(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null && elements[i].equals(key)) { + elements[i] = null; + Object result = elements[i + 1]; + elements[i + 1] = null; + count--; + return result; + } + } + return null; + } + + /** + * @see Map#size() + */ + @Override + public int size() { + return count; + } + + /** + * Creates a new hash map with the same contents as this map. + */ + private HashMap toHashMap() { + HashMap result = new HashMap(size()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.put(elements[i], elements[i + 1]); + } + } + return result; + } + + /** + * @see Map#values() + * + * Note: 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()); + for (int i = 1; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.add(elements[i]); + } + } + return result; + } +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java new file mode 100644 index 0000000000..4744e198b6 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java @@ -0,0 +1,310 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - Bug 311863 Ordered Lock lost after interrupt + *******************************************************************************/ +package org.eclipse.core.internal.jobs; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.jobs.ILock; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * A lock used to control write access to an exclusive resource. + * + * The lock avoids circular waiting deadlocks by detecting the deadlocks + * and resolving them through the suspension of all locks owned by one + * of the threads involved in the deadlock. This makes it impossible for n such + * locks to deadlock while waiting for each other. The down side is that this means + * that during an interval when a process owns a lock, it can be forced + * to give the lock up and wait until all locks it requires become + * available. This removes the feature of exclusive access to the + * resource in contention for the duration between acquire() and + * release() calls. + * + * The lock implementation prevents starvation by granting the + * lock in the same order in which acquire() requests arrive. In + * this scheme, starvation is only possible if a thread retains + * a lock indefinitely. + */ +public class OrderedLock implements ILock, ISchedulingRule { + + private static final boolean DEBUG = false; + /** + * Locks are sequentially ordered for debugging purposes. + */ + private static int nextLockNumber = 0; + /** + * The thread of the operation that currently owns the lock. + */ + private volatile Thread currentOperationThread; + /** + * Records the number of successive acquires in the same + * thread. The lock is released only when the depth + * reaches zero. + */ + private int depth; + /** + * The manager that implements the deadlock detection and resolution protocol. + */ + private final LockManager manager; + private final int number; + + /** + * Queue of semaphores for threads currently waiting + * on the lock. This queue is not thread-safe, so access + * to this queue must be synchronized on the lock instance. + */ + private final Queue operations = new Queue(); + + /** + * Creates a new workspace lock. + */ + OrderedLock(LockManager manager) { + this.manager = manager; + this.number = nextLockNumber++; + } + + @Override + public void acquire() { + //spin until the lock is successfully acquired + //NOTE: spinning here allows the UI thread to service pending syncExecs + //if the UI thread is waiting to acquire a lock. + boolean interrupted = false; + while (true) { + try { + if (acquire(Long.MAX_VALUE)) + break; + } catch (InterruptedException e) { + interrupted = true; + } + } + //preserve thread interrupt state + if (interrupted) + Thread.currentThread().interrupt(); + } + + @Override + public boolean acquire(long delay) throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + + boolean success = false; + if (delay <= 0) + return attempt(); + Semaphore semaphore = createSemaphore(); + if (semaphore == null) + return true; + if (DEBUG) + System.out.println("[" + Thread.currentThread() + "] Operation waiting to be executed... " + this); //$NON-NLS-1$ //$NON-NLS-2$ + success = doAcquire(semaphore, delay); + manager.resumeSuspendedLocks(Thread.currentThread()); + if (DEBUG) + System.out.println("[" + Thread.currentThread() + //$NON-NLS-1$ + (success ? "] Operation started... " : "] Operation timed out... ") + this); //$NON-NLS-1$ //$NON-NLS-2$ //} + if (!success && Thread.interrupted()) + throw new InterruptedException(); + return success; + } + + /** + * Attempts to acquire the lock. Returns false if the lock is not available and + * true if the lock has been successfully acquired. + */ + private synchronized boolean attempt() { + //return true if we already own the lock + //also, if nobody is waiting, grant the lock immediately + if ((currentOperationThread == Thread.currentThread()) || (currentOperationThread == null && operations.isEmpty())) { + depth++; + setCurrentOperationThread(Thread.currentThread()); + return true; + } + return false; + } + + @Override + public boolean contains(ISchedulingRule rule) { + return rule == this; + } + + /** + * Returns null if acquired and a Semaphore object otherwise. If a + * waiting semaphore already exists for this thread, it will be returned, + * otherwise a new semaphore will be created, enqueued, and returned. + */ + private synchronized Semaphore createSemaphore() { + return attempt() ? null : enqueue(new Semaphore(Thread.currentThread())); + } + + /** + * Attempts to acquire this lock. Callers will block until this lock comes available to + * them, or until the specified delay has elapsed. + */ + private boolean doAcquire(Semaphore semaphore, long delay) { + boolean success = false; + //notify hook to service pending syncExecs before falling asleep + if (manager.aboutToWait(this.currentOperationThread)) { + //hook granted immediate access + //remove semaphore for the lock request from the queue + //do not log in graph because this thread did not really get the lock + removeFromQueue(semaphore); + depth++; + manager.addLockThread(currentOperationThread, this); + return true; + } + //Make sure the semaphore is in the queue before we start waiting + //It might have been removed from the queue while servicing syncExecs + //This is will return our existing semaphore if it is still in the queue + semaphore = createSemaphore(); + if (semaphore == null) + return true; + final Thread currentThread = Thread.currentThread(); + manager.addLockWaitThread(currentThread, this); + try { + success = semaphore.acquire(delay); + } catch (InterruptedException e) { + if (DEBUG) + System.out.println("[" + currentThread + "] Operation interrupted while waiting... :-|"); //$NON-NLS-1$ //$NON-NLS-2$ + //remember the interrupt to throw it later + currentThread.interrupt(); + } + return updateOperationQueue(semaphore, success); + } + + /** + * Releases this lock from the thread that used to own it. + * Grants this lock to the next thread in the queue. + */ + private synchronized void doRelease() { + //notify hook + manager.aboutToRelease(); + depth = 0; + Semaphore next = (Semaphore) operations.peek(); + setCurrentOperationThread(null); + if (next != null) + next.release(); + } + + /** + * If there is another semaphore with the same runnable in the + * queue, the other is returned and the new one is not added. + */ + private synchronized Semaphore enqueue(Semaphore newSemaphore) { + Semaphore semaphore = (Semaphore) operations.get(newSemaphore); + if (semaphore == null) { + operations.enqueue(newSemaphore); + return newSemaphore; + } + return semaphore; + } + + /** + * Suspend this lock by granting the lock to the next lock in the queue. + * Return the depth of the suspended lock. + */ + protected int forceRelease() { + int oldDepth = depth; + doRelease(); + return oldDepth; + } + + @Override + public int getDepth() { + return depth; + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + return rule == this; + } + + @Override + public void release() { + if (depth == 0) + return; + //only release the lock when the depth reaches zero + Assert.isTrue(depth >= 0, "Lock released too many times"); //$NON-NLS-1$ + if (--depth == 0) + doRelease(); + else + manager.removeLockThread(currentOperationThread, this); + } + + /** + * Removes a semaphore from the queue of waiting operations. + * + * @param semaphore The semaphore to remove + */ + private synchronized void removeFromQueue(Semaphore semaphore) { + operations.remove(semaphore); + } + + /** + * If newThread is null, release this lock from its previous owner. + * If newThread is not null, grant this lock to newThread. + */ + private void setCurrentOperationThread(Thread newThread) { + if ((currentOperationThread != null) && (newThread == null)) + manager.removeLockThread(currentOperationThread, this); + this.currentOperationThread = newThread; + if (currentOperationThread != null) + manager.addLockThread(currentOperationThread, this); + } + + /** + * Forces the lock to be at the given depth. + * Used when re-acquiring a suspended lock. + */ + protected void setDepth(int newDepth) { + for (int i = depth; i < newDepth; i++) { + manager.addLockThread(currentOperationThread, this); + } + this.depth = newDepth; + } + + /** + * For debugging purposes only. + */ + @Override + public String toString() { + return "OrderedLock (" + number + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * This lock has just been granted to a new thread (the thread waited for it). + * Remove the request from the queue and update both the graph and the lock. + */ + private synchronized void updateCurrentOperation() { + operations.dequeue(); + setCurrentOperationThread(Thread.currentThread()); + } + + /** + * We have finished waiting on the given semaphore. Update the operation queue according + * to whether we succeeded in obtaining the lock. + * + * @param semaphore The semaphore that we waited on + * @param acquired true if we successfully acquired the semaphore, and false otherwise + * @return whether the lock was successfully obtained + */ + private synchronized boolean updateOperationQueue(Semaphore semaphore, boolean acquired) { + // Bug 311863 - Semaphore may have been released concurrently, so check again before discarding it + if (!acquired) + acquired = semaphore.attempt(); + if (acquired) { + depth++; + updateCurrentOperation(); + } else { + removeFromQueue(semaphore); + manager.removeLockWaitThread(Thread.currentThread(), this); + } + return acquired; + } + +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java new file mode 100644 index 0000000000..03c1bb94cd --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * 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.*; + +/** + * A Queue of objects. + */ +public class Queue { + protected Object[] elements; + protected int head; + protected boolean reuse; + protected int tail; + + public Queue() { + this(20, false); + } + + /** + * The parameter reuse indicates what do you want to happen with + * the object reference when you remove it from the queue. If + * reuse is false the queue no longer holds a reference to the + * object when it is removed. If reuse is true you can use the + * method getNextAvailableObject to get an used object, set its + * new values and add it again to the queue. + */ + public Queue(int size, boolean reuse) { + elements = new Object[size]; + head = tail = 0; + this.reuse = reuse; + } + + /** + * Adds an object to the tail of the queue. + */ + public void enqueue(Object element) { + int newTail = increment(tail); + if (newTail == head) { + grow(); + newTail = tail + 1; + } + elements[tail] = element; + tail = newTail; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to decrement an index in the queue. + */ + public int decrement(int index) { + return (index == 0) ? (elements.length - 1) : index - 1; + } + + public Iterator elements() { + /**/ + if (isEmpty()) + return new ArrayList(0).iterator(); + + /* if head < tail we can use the same array */ + if (head <= tail) + return Arrays.asList(elements).iterator(); + + /* otherwise we need to create a new array */ + Object[] newElements = new Object[size()]; + int end = (elements.length - head); + System.arraycopy(elements, head, newElements, 0, end); + System.arraycopy(elements, 0, newElements, end, tail); + return Arrays.asList(newElements).iterator(); + } + + public Object get(Object o) { + int index = head; + while (index != tail) { + if (elements[index].equals(o)) + return elements[index]; + index = increment(index); + } + return null; + } + + /** + * Removes the given object from the queue. Shifts the underlying array. + */ + public boolean remove(Object o) { + int index = head; + //find the object to remove + while (index != tail) { + if (elements[index].equals(o)) + break; + index = increment(index); + } + //if element wasn't found, return + if (index == tail) + return false; + //store a reference to it (needed for reuse of objects) + Object toRemove = elements[index]; + int nextIndex = -1; + while (index != tail) { + nextIndex = increment(index); + if (nextIndex != tail) + elements[index] = elements[nextIndex]; + + index = nextIndex; + } + //decrement tail + tail = decrement(tail); + + //if objects are reused, transfer the reference that is removed to the end of the queue + //otherwise set the element after the last one to null (to avoid duplicate references) + elements[tail] = reuse ? toRemove : null; + return true; + } + + protected void grow() { + int newSize = (int) (elements.length * 1.5); + Object[] newElements = new Object[newSize]; + if (tail >= head) + System.arraycopy(elements, head, newElements, head, size()); + else { + int newHead = newSize - (elements.length - head); + System.arraycopy(elements, 0, newElements, 0, tail + 1); + System.arraycopy(elements, head, newElements, newHead, (newSize - newHead)); + head = newHead; + } + elements = newElements; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to increment an index in the queue. + */ + public int increment(int index) { + return (index == (elements.length - 1)) ? 0 : index + 1; + } + + public boolean isEmpty() { + return tail == head; + } + + public Object peek() { + return elements[head]; + } + + /** + * Removes an returns the item at the head of the queue. + */ + public Object dequeue() { + if (isEmpty()) + return null; + Object result = peek(); + if (!reuse) + elements[head] = null; + head = increment(head); + return result; + } + + public int size() { + return tail > head ? (tail - head) : ((elements.length - head) + tail); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("["); //$NON-NLS-1$ + if (!isEmpty()) { + Iterator it = elements(); + while (true) { + sb.append(it.next()); + if (it.hasNext()) + sb.append(", "); //$NON-NLS-1$ + else + break; + } + } + sb.append("]"); //$NON-NLS-1$ + return sb.toString(); + } +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java new file mode 100644 index 0000000000..a169b01278 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - Bug 311863 Ordered Lock lost after interrupt + *******************************************************************************/ +package org.eclipse.core.internal.jobs; + +public class Semaphore { + protected long notifications; + protected Runnable runnable; + + public Semaphore(Runnable runnable) { + this.runnable = runnable; + notifications = 0; + } + + /** + * Attempts to acquire this semaphore. Returns true if it was successfully acquired, + * and false otherwise. + */ + public synchronized boolean acquire(long delay) throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + long start = System.currentTimeMillis(); + long timeLeft = delay; + while (true) { + if (notifications > 0) { + notifications--; + return true; + } + if (timeLeft <= 0) + return false; + wait(timeLeft); + timeLeft = start + delay - System.currentTimeMillis(); + } + } + + /** + * Attempt to acquire the semaphore without waiting. + * Returns true if successfully acquired, false otherwise. + */ + public synchronized boolean attempt() { + if (notifications > 0) { + notifications--; + return true; + } + return false; + } + + @Override + public boolean equals(Object obj) { + return (runnable == ((Semaphore) obj).runnable); + } + + @Override + public int hashCode() { + return runnable == null ? 0 : runnable.hashCode(); + } + + public synchronized void release() { + notifications++; + notifyAll(); + } + + // for debug only + @Override + public String toString() { + return "Semaphore(" + runnable + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java new file mode 100644 index 0000000000..1567ac0bcd --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java @@ -0,0 +1,451 @@ +/******************************************************************************* + * 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.internal.jobs; + +import org.eclipse.core.internal.runtime.RuntimeLog; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.*; + +/** + * Captures the implicit job state for a given thread. + */ +class ThreadJob extends Job { + + /** + * Set to true if this thread job is running in a thread that did + * not own a rule already. This means it needs to acquire the + * rule during beginRule, and must release the rule during endRule. + */ + protected boolean acquireRule = false; + + /** + * Indicates that this thread job did report to the progress manager + * that it will be blocked, and therefore when it begins it must + * be reported to the job manager when it is no longer blocked. + */ + boolean isBlocked = false; + + /** + * True if this ThreadJob has begun execution + * @GuardedBy("this") + */ + protected boolean isRunning = false; + + /** + * Used for diagnosing mismatched begin/end pairs. This field + * is only used when in debug mode, to capture the stack trace + * of the last call to beginRule. + */ + private RuntimeException lastPush = null; + /** + * The actual job that is running in the thread that this + * ThreadJob represents. This will be null if this thread + * job is capturing a rule acquired outside of a job. + * @GuardedBy("JobManager.implicitJobs") + */ + protected Job realJob; + /** + * The stack of rules that have been begun in this thread, but not yet ended. + * @GuardedBy("JobManager.implicitJobs") + */ + private ISchedulingRule[] ruleStack; + /** + * Rule stack pointer. + * INV: 0 <= top <= ruleStack.length + * @GuardedBy("JobManager.implicitJobs") + */ + private int top; + + /** + * Waiting state for thread jobs is independent of the internal state. When + * this variable is true, this ThreadJob is waiting in joinRun() + * @GuardedBy("jobStateLock") + */ + boolean isWaiting; + + ThreadJob(ISchedulingRule rule) { + super("Implicit Job"); //$NON-NLS-1$ + setSystem(true); + // calling setPriority will try to acquire JobManager.lock, breaking + // lock acquisition protocol. Since we are constructing this thread, + // we can call internalSetPriority + ((InternalJob) this).internalSetPriority(Job.INTERACTIVE); + ruleStack = new ISchedulingRule[2]; + top = -1; + ((InternalJob) this).internalSetRule(rule); + } + + boolean isResumingAfterYield() { + return false; + } + + /** + * An endRule was called that did not match the last beginRule in + * the stack. Report and log a detailed informational message. + * @param rule The rule that was popped + * @GuardedBy("JobManager.implicitJobs") + */ + private void illegalPop(ISchedulingRule rule) { + StringBuffer buf = new StringBuffer("Attempted to endRule: "); //$NON-NLS-1$ + buf.append(rule); + if (top >= 0 && top < ruleStack.length) { + buf.append(", does not match most recent begin: "); //$NON-NLS-1$ + buf.append(ruleStack[top]); + } else { + if (top < 0) + buf.append(", but there was no matching beginRule"); //$NON-NLS-1$ + else + buf.append(", but the rule stack was out of bounds: " + top); //$NON-NLS-1$ + } + buf.append(". See log for trace information if rule tracing is enabled."); //$NON-NLS-1$ + String msg = buf.toString(); + if (JobManager.DEBUG || JobManager.DEBUG_BEGIN_END) { + System.out.println(msg); + Throwable t = lastPush == null ? new IllegalArgumentException() : lastPush; + IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, t); + RuntimeLog.log(error); + } + Assert.isLegal(false, msg); + } + + /** + * Client has attempted to begin a rule that is not contained within + * the outer rule. + */ + private void illegalPush(ISchedulingRule pushRule, ISchedulingRule baseRule) { + StringBuffer buf = new StringBuffer("Attempted to beginRule: "); //$NON-NLS-1$ + buf.append(pushRule); + buf.append(", does not match outer scope rule: "); //$NON-NLS-1$ + buf.append(baseRule); + String msg = buf.toString(); + if (JobManager.DEBUG) { + System.out.println(msg); + IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, new IllegalArgumentException()); + RuntimeLog.log(error); + } + Assert.isLegal(false, msg); + + } + + /** + * Returns true if the monitor is canceled, and false otherwise. + * Protects the caller from exception in the monitor implementation. + */ + static private boolean isCanceled(IProgressMonitor monitor) { + try { + return monitor.isCanceled(); + } catch (RuntimeException e) { + //logged message should not be translated + IStatus status = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "ThreadJob.isCanceled", e); //$NON-NLS-1$ + RuntimeLog.log(status); + } + return false; + } + + /** + * Returns true if this thread job was scheduled and actually started running. + * @GuardedBy("this") + */ + synchronized boolean isRunning() { + return isRunning; + } + + /** + * A reentrant method which will run this ThreadJob immediately if there + * are no existing jobs with conflicting rules, or block until the rule can be acquired. If this + * job must block, the LockListener is given a chance to override. + * If override is not granted, then this method will block until the rule is available. If + * LockListener#canBlock returns true, then the monitor + * will not be periodically checked for cancellation. It will only be rechecked if this + * thread is interrupted. If LockListener#canBlock returns false The + * monitor will be checked periodically for cancellation. + * + * When a UI is present, it is recommended that the LockListener + * should not allow the UI thread to block without checking the monitor. This + * ensures that the UI remains responsive. + * + * @see LockListener#aboutToWait(Thread) + * @see LockListener#canBlock() + * @see JobManager#transferRule(ISchedulingRule, Thread) + + * @return this, or the ThreadJob instance that was + * unblocked (due to transferRule) in the case of reentrant invocations of this method. + * + * @param monitor - The IProgressMonitor used to report blocking status and + * cancellation. + * + * @throws OperationCanceledException if this job was canceled before it was started. + */ + static ThreadJob joinRun(ThreadJob threadJob, IProgressMonitor monitor) { + if (isCanceled(monitor)) + throw new OperationCanceledException(); + // check if there is a blocking thread before waiting + InternalJob blockingJob = manager.findBlockingJob(threadJob); + Thread blocker = blockingJob == null ? null : blockingJob.getThread(); + try { + // just return if lock listener decided to grant immediate access + if (manager.getLockManager().aboutToWait(blocker)) + return threadJob; + return waitForRun(threadJob, monitor, blockingJob, blocker); + } finally { + manager.getLockManager().aboutToRelease(); + } + } + + static ThreadJob waitForRun(ThreadJob threadJob, IProgressMonitor monitor, InternalJob blockingJob, Thread blocker) { + // Ask lock manager if it safe to block this thread + final boolean canBlock = manager.getLockManager().canBlock(); + ThreadJob result = threadJob; + boolean interrupted = false; + boolean waiting = false; + try { + waitStart(threadJob, monitor, blockingJob); + manager.implicitJobs.addWaiting(threadJob); + waiting = true; + // If we're allowed to block this thread we won't be checking the monitor. In order + // to respond to cancellation, register this monitor with the internal JobManager + // worker thread. The worker thread will check for cancellation and will interrupt + // this thread when the monitor is canceled. T + if (canBlock) + manager.beginMonitoring(threadJob, monitor); + final Thread currentThread = Thread.currentThread(); + + // Ultimately, this loop will wait until the job "runs" (acquires the rule) + // or is canceled. However, there are many ways for that to occur. + // The exit conditions of this loop are: + // 1) This job no longer conflicts with any other running job. + // 2) The LockManager#aboutToWait allowed this thread to run. + // This usually occurs during reentrant situations. i.e. This is a UI thread, + // and a syncExec is performed from conflicting job/thread. + // 3) A rule is transfered to this thread. This can be invoked programmatically, + // or commonly in JFace via ModalContext (for wizards/etc). + // 4) Monitor is canceled. + while (true) { + // monitor is foreign code so do not hold locks while calling into monitor + if (isCanceled(monitor)) + // Condition #4. + throw new OperationCanceledException(); + // Try to run the job. If result is null, this job was allowed to run. + // If the result is successful, atomically release thread from waiting + // status. + blockingJob = manager.runNow(threadJob, true); + if (blockingJob == null) { + // Condition #1. + waiting = false; + return threadJob; + } + blocker = blockingJob == null ? null : blockingJob.getThread(); + // the rule could have been transferred to this thread while we were waiting + if (blocker == currentThread && blockingJob instanceof ThreadJob) { + // now we are just the nested acquire case + result = (ThreadJob) blockingJob; + result.push(threadJob.getRule()); + result.isBlocked = threadJob.isBlocked; + // Condition #3. + return result; + } + // just return if lock listener decided to grant immediate access + if (manager.getLockManager().aboutToWait(blocker)) + // Condition #2. + return threadJob; + + // Notify the lock manager that we're about to block waiting for the scheduling rule + manager.getLockManager().addLockWaitThread(currentThread, threadJob.getRule()); + synchronized (blockingJob.jobStateLock) { + try { + // Wait until we are no longer definitely blocked (not running). + // The actual exit conditions are listed above at the beginning of + // this while loop + int state = blockingJob.getState(); + //ensure we don't wait forever if the blocker is waiting, because it might have yielded to me + if (state == Job.RUNNING && canBlock) + blockingJob.jobStateLock.wait(); + else if (state != Job.NONE) + blockingJob.jobStateLock.wait(250); + } catch (InterruptedException e) { + // This thread may be interrupted via two common scenarios. 1) If + // the UISynchronizer is in use and this thread is a UI thread + // and a syncExec() is performed, this thread will be interrupted + // every 1000ms. 2) If this thread is allowed to be blocked and + // the progress monitor was canceled, the internal JobManager + // worker thread will interrupt this thread so cancellation can + // be carried out. + interrupted = true; + } + } + // Going around the loop again. Ensure we're not marked as waiting for the thread + // as external code is run via the monitor (Bug 262032). + manager.getLockManager().removeLockWaitThread(currentThread, threadJob.getRule()); + } + } finally { + if (interrupted) + Thread.currentThread().interrupt(); + //only update the lock state if we ended up using the thread job that was given to us + waitEnd(threadJob, threadJob == result, monitor); + if (threadJob == result) { + if (waiting) + manager.implicitJobs.removeWaiting(threadJob); + } + if (canBlock) + // must unregister monitoring this job + manager.endMonitoring(threadJob); + } + } + + /** + * Pops a rule. Returns true if it was the last rule for this thread + * job, and false otherwise. + * @GuardedBy("JobManager.implicitJobs") + */ + boolean pop(ISchedulingRule rule) { + if (top < 0 || ruleStack[top] != rule) + illegalPop(rule); + ruleStack[top--] = null; + return top < 0; + } + + /** + * Adds a new scheduling rule to the stack of rules for this thread. Throws + * a runtime exception if the new rule is not compatible with the base + * scheduling rule for this thread. + * @GuardedBy("JobManager.implicitJobs") + */ + void push(final ISchedulingRule rule) { + final ISchedulingRule baseRule = getRule(); + if (++top >= ruleStack.length) { + ISchedulingRule[] newStack = new ISchedulingRule[ruleStack.length * 2]; + System.arraycopy(ruleStack, 0, newStack, 0, ruleStack.length); + ruleStack = newStack; + } + ruleStack[top] = rule; + if (JobManager.DEBUG_BEGIN_END) + lastPush = (RuntimeException) new RuntimeException().fillInStackTrace(); + //check for containment last because we don't want to fail again on endRule + if (baseRule != null && rule != null && !(baseRule.contains(rule) && baseRule.isConflicting(rule))) + illegalPush(rule, baseRule); + } + + /** + * Reset all of this job's fields so it can be reused. Returns false if + * reuse is not possible + * @GuardedBy("JobManager.implicitJobs") + */ + boolean recycle() { + //don't recycle if still running for any reason + if (getState() != Job.NONE) + return false; + //clear and reset all fields + acquireRule = isRunning = isBlocked = false; + realJob = null; + setRule(null); + setThread(null); + if (ruleStack.length != 2) + ruleStack = new ISchedulingRule[2]; + else + ruleStack[0] = ruleStack[1] = null; + top = -1; + return true; + } + + /** (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public IStatus run(IProgressMonitor monitor) { + synchronized (this) { + isRunning = true; + } + return ASYNC_FINISH; + } + + /** + * Records the job that is actually running in this thread, if any + * @param realJob The running job + * @GuardedBy("JobManager.implicitJobs") + */ + void setRealJob(Job realJob) { + this.realJob = realJob; + } + + /** + * Returns true if this job should cause a self-canceling job + * to cancel itself, and false otherwise. + * @GuardedBy("JobManager.implicitJobs") + */ + boolean shouldInterrupt() { + return realJob == null ? true : !realJob.isSystem(); + } + + /* (non-javadoc) + * For debugging purposes only + */ + @Override + public String toString() { + StringBuffer buf = new StringBuffer("ThreadJob"); //$NON-NLS-1$ + buf.append('(').append(realJob).append(',').append(getRuleStack()).append(')'); + return buf.toString(); + } + + String getRuleStack() { + StringBuffer buf = new StringBuffer(); + buf.append('['); + for (int i = 0; i <= top && i < ruleStack.length; i++) { + buf.append(ruleStack[i]); + if (i != top) + buf.append(','); + } + buf.append(']'); + return buf.toString(); + } + + /** + * Reports that this thread was blocked, but is no longer blocked and is able + * to proceed. + * @param monitor The monitor to report unblocking to. + */ + static private void waitEnd(ThreadJob threadJob, boolean updateLockManager, IProgressMonitor monitor) { + if (updateLockManager) { + final LockManager lockManager = manager.getLockManager(); + final Thread currentThread = Thread.currentThread(); + if (threadJob.isRunning()) { + lockManager.addLockThread(currentThread, threadJob.getRule()); + //need to re-acquire any locks that were suspended while this thread was blocked on the rule + lockManager.resumeSuspendedLocks(currentThread); + } else { + //tell lock manager that this thread gave up waiting + lockManager.removeLockWaitThread(currentThread, threadJob.getRule()); + } + } + if (threadJob.isBlocked) { + threadJob.isBlocked = false; + manager.reportUnblocked(monitor); + } + } + + /** + * Indicates the start of a wait on a scheduling rule. Report the + * blockage to the progress manager. + * @param monitor The monitor to report blocking to + * @param blockingJob The job that is blocking this thread, or null + */ + static private void waitStart(ThreadJob threadJob, IProgressMonitor monitor, InternalJob blockingJob) { + threadJob.isBlocked = true; + manager.reportBlocked(monitor, blockingJob); + } + + /** + * ThreadJobs are one-shot jobs, and they must ignore all attempts to schedule them. + */ + @Override + public boolean shouldSchedule() { + return false; + } +} \ No newline at end of file diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java new file mode 100644 index 0000000000..0fa63c3a3e --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.internal.jobs; + +import org.eclipse.core.internal.runtime.RuntimeLog; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; + +/** + * A worker thread processes jobs supplied to it by the worker pool. When + * the worker pool gives it a null job, the worker dies. + */ +public class Worker extends Thread { + //worker number used for debugging purposes only + private static int nextWorkerNumber = 0; + private volatile InternalJob currentJob; + private final WorkerPool pool; + + public Worker(WorkerPool pool) { + super("Worker-" + nextWorkerNumber++); //$NON-NLS-1$ + this.pool = pool; + //set the context loader to avoid leaking the current context loader + //for the thread that spawns this worker (bug 98376) + setContextClassLoader(pool.defaultContextLoader); + } + + /** + * Returns the currently running job, or null if none. + */ + public Job currentJob() { + return (Job) currentJob; + } + + private IStatus handleException(InternalJob job, Throwable t) { + String message = NLS.bind(JobMessages.jobs_internalError, job.getName()); + return new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, message, t); + } + + @Override + public void run() { + setPriority(Thread.NORM_PRIORITY); + try { + while ((currentJob = pool.startJob(this)) != null) { + IStatus result = Status.OK_STATUS; + try { + result = currentJob.run(currentJob.getProgressMonitor()); + } catch (OperationCanceledException e) { + result = Status.CANCEL_STATUS; + } catch (Exception e) { + result = handleException(currentJob, e); + } catch (ThreadDeath e) { + //must not consume thread death + result = handleException(currentJob, e); + throw e; + } catch (Error e) { + result = handleException(currentJob, e); + } finally { + //clear interrupted state for this thread + Thread.interrupted(); + //result must not be null + if (result == null) + result = handleException(currentJob, new NullPointerException()); + pool.endJob(currentJob, result); + currentJob = null; + //reset thread priority in case job changed it + setPriority(Thread.NORM_PRIORITY); + } + } + } catch (Throwable t) { + RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "Unhandled error", t)); //$NON-NLS-1$ + } finally { + currentJob = null; + pool.endWorker(this); + } + } +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java new file mode 100644 index 0000000000..ee38ffdef7 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * 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.Assert; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.jobs.Job; + +/** + * Maintains a pool of worker threads. Threads are constructed lazily as + * required, and are eventually discarded if not in use for awhile. This class + * maintains the thread creation/destruction policies for the job manager. + * + * Implementation note: all the data structures of this class are protected + * by the instance's object monitor. To avoid deadlock with third party code, + * this lock is never held when calling methods outside this class that may in + * turn use locks. + */ +class WorkerPool { + /** + * Threads not used by their best before timestamp are destroyed. + */ + private static final int BEST_BEFORE = 60000; + /** + * There will always be at least MIN_THREADS workers in the pool. + */ + private static final int MIN_THREADS = 1; + /** + * Use the busy thread count to avoid starting new threads when a living + * thread is just doing house cleaning (notifying listeners, etc). + */ + private int busyThreads = 0; + + /** + * The default context class loader to use when creating worker threads. + */ + protected final ClassLoader defaultContextLoader; + + /** + * Records whether new worker threads should be daemon threads. + */ + private boolean isDaemon = false; + + private JobManager manager; + /** + * The number of workers in the threads array + */ + private int numThreads = 0; + /** + * The number of threads that are currently sleeping + */ + private int sleepingThreads = 0; + /** + * The living set of workers in this pool. + */ + private Worker[] threads = new Worker[10]; + + protected WorkerPool(JobManager manager) { + this.manager = manager; + this.defaultContextLoader = Thread.currentThread().getContextClassLoader(); + } + + /** + * Adds a worker to the list of workers. + */ + private synchronized void add(Worker worker) { + int size = threads.length; + if (numThreads + 1 > size) { + Worker[] newThreads = new Worker[2 * size]; + System.arraycopy(threads, 0, newThreads, 0, size); + threads = newThreads; + } + threads[numThreads++] = worker; + } + + private synchronized void decrementBusyThreads() { + //impossible to have less than zero busy threads + if (--busyThreads < 0) { + if (JobManager.DEBUG) + Assert.isTrue(false, Integer.toString(busyThreads)); + busyThreads = 0; + } + } + + /** + * Signals the end of a job. Note that this method can be called under + * OutOfMemoryError conditions and thus must be paranoid about allocating objects. + */ + protected void endJob(InternalJob job, IStatus result) { + try { + //need to end rule in graph before ending job so that 2 threads + //do not become the owners of the same rule in the graph + if ((job.getRule() != null) && !(job instanceof ThreadJob)) { + //remove any locks this thread may be owning on that rule + manager.getLockManager().removeLockCompletely(Thread.currentThread(), job.getRule()); + } + manager.endJob(job, result, true); + //ensure this thread no longer owns any scheduling rules + manager.implicitJobs.endJob(job); + } finally { + decrementBusyThreads(); + } + } + + /** + * Signals the death of a worker thread. Note that this method can be called under + * OutOfMemoryError conditions and thus must be paranoid about allocating objects. + */ + protected synchronized void endWorker(Worker worker) { + if (remove(worker) && JobManager.DEBUG) + JobManager.debug("worker removed from pool: " + worker); //$NON-NLS-1$ + } + + private synchronized void incrementBusyThreads() { + //impossible to have more busy threads than there are threads + if (++busyThreads > numThreads) { + if (JobManager.DEBUG) + Assert.isTrue(false, Integer.toString(busyThreads) + ',' + numThreads); + busyThreads = numThreads; + } + } + + /** + * Notification that a job has been added to the queue. Wake a worker, + * creating a new worker if necessary. The provided job may be null. + */ + protected synchronized void jobQueued() { + //if there is a sleeping thread, wake it up + if (sleepingThreads > 0) { + notify(); + return; + } + //create a thread if all threads are busy + if (busyThreads >= numThreads) { + Worker worker = new Worker(this); + worker.setDaemon(isDaemon); + add(worker); + if (JobManager.DEBUG) + JobManager.debug("worker added to pool: " + worker); //$NON-NLS-1$ + worker.start(); + return; + } + } + + /** + * Remove a worker thread from our list. + * @return true if a worker was removed, and false otherwise. + */ + private synchronized boolean remove(Worker worker) { + for (int i = 0; i < threads.length; i++) { + if (threads[i] == worker) { + System.arraycopy(threads, i + 1, threads, i, numThreads - i - 1); + threads[--numThreads] = null; + return true; + } + } + return false; + } + + /** + * Sets whether threads created in the worker pool should be daemon threads. + */ + void setDaemon(boolean value) { + this.isDaemon = value; + } + + protected synchronized void shutdown() { + notifyAll(); + } + + /** + * Sleep for the given duration or until woken. + */ + private synchronized void sleep(long duration) { + sleepingThreads++; + busyThreads--; + if (JobManager.DEBUG) + JobManager.debug("worker sleeping for: " + duration + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + try { + wait(duration); + } catch (InterruptedException e) { + if (JobManager.DEBUG) + JobManager.debug("worker interrupted while waiting... :-|"); //$NON-NLS-1$ + } finally { + sleepingThreads--; + busyThreads++; + } + } + + /** + * Returns a new job to run. Returns null if the thread should die. + */ + protected InternalJob startJob(Worker worker) { + //if we're above capacity, kill the thread + synchronized (this) { + if (!manager.isActive()) { + //must remove the worker immediately to prevent all threads from expiring + endWorker(worker); + return null; + } + //set the thread to be busy now in case of reentrant scheduling + incrementBusyThreads(); + } + Job job = null; + try { + job = manager.startJob(worker); + //spin until a job is found or until we have been idle for too long + long idleStart = System.currentTimeMillis(); + while (manager.isActive() && job == null) { + long hint = manager.sleepHint(); + if (hint > 0) + sleep(Math.min(hint, BEST_BEFORE)); + job = manager.startJob(worker); + //if we were already idle, and there are still no new jobs, then + // the thread can expire + synchronized (this) { + if (job == null && (System.currentTimeMillis() - idleStart > BEST_BEFORE) && (numThreads - busyThreads) > MIN_THREADS) { + //must remove the worker immediately to prevent all threads from expiring + endWorker(worker); + return null; + } + } + //if we didn't sleep but there was no job available, make sure we sleep to avoid a tight loop (bug 260724) + if (hint <= 0 && job == null) + sleep(50); + } + if (job != null) { + //if this job has a rule, then we are essentially acquiring a lock + if ((job.getRule() != null) && !(job instanceof ThreadJob)) { + //don't need to re-acquire locks because it was not recorded in the graph + //that this thread waited to get this rule + manager.getLockManager().addLockThread(Thread.currentThread(), job.getRule()); + } + //see if we need to wake another worker + if (manager.sleepHint() < InternalJob.T_INFINITE) + jobQueued(); + } + } finally { + //decrement busy thread count if we're not running a job + if (job == null) + decrementBusyThreads(); + } + return job; + } +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.properties b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.properties new file mode 100644 index 0000000000..8451b403e6 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.properties @@ -0,0 +1,21 @@ +############################################################################### +# 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 +############################################################################### +### Runtime jobs plugin messages + +### Job Manager and Locks +jobs_blocked0=The user operation is waiting for background work to complete. +jobs_blocked1=The user operation is waiting for \"{0}\" to complete. +jobs_internalError=An internal error occurred during: \"{0}\". +jobs_waitFamSub={0} operations remaining. +jobs_waitFamSubOne={0} operation remaining. + +### metadata +meta_pluginProblems = Problems occurred when invoking code from plug-in: \"{0}\". diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java new file mode 100644 index 0000000000..5cbf0839a6 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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.runtime.jobs; + +import org.eclipse.core.runtime.IStatus; + +/** + * An event describing a change to the state of a job. + * + * @see IJobChangeListener + * @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 IJobChangeEvent { + /** + * The amount of time in milliseconds to wait after scheduling the job before it + * should be run, or -1 if not applicable for this type of event. + * This value is only applicable for the scheduled event. + * + * @return the delay time for this event + */ + public long getDelay(); + + /** + * The job on which this event occurred. + * + * @return the job for this event + */ + public Job getJob(); + + /** + * The result returned by the job's run method, or null if + * not applicable. This value is only applicable for the done event. + * + * @return the status for this event + */ + public IStatus getResult(); + + /** + * 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. This value is only applicable for the done event. + * + * @return the job group status for this event, or null + * @since 3.7 + */ + public IStatus getJobGroupResult(); +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java new file mode 100644 index 0000000000..26ee6e1673 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * 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; + +/** + * Callback interface for clients interested in being notified when jobs change state. + *

                          + * A single job listener instance can be added either to the job manager, for notification + * of all scheduled jobs, or to any set of individual jobs. A single listener instance should + * not be added to both the job manager, and to individual jobs (such a listener may + * receive duplicate notifications). + *

                          + * Clients should not rely on the result of the Job#getState() + * method on jobs for which notification is occurring. Listeners are notified of + * all job state changes, but whether the state change occurs before, during, or + * after listeners are notified is unspecified. + *

                          + * Clients may implement this interface. + *

                          + * @see JobChangeAdapter + * @see IJobManager#addJobChangeListener(IJobChangeListener) + * @see IJobManager#removeJobChangeListener(IJobChangeListener) + * @see Job#addJobChangeListener(IJobChangeListener) + * @see Job#getState() + * @see Job#removeJobChangeListener(IJobChangeListener) + * @since 3.0 + */ +public interface IJobChangeListener { + /** + * Notification that a job is about to be run. Listeners are allowed to sleep, cancel, + * or change the priority of the job before it is started (and as a result may prevent + * the run from actually occurring). + * + * @param event the event details + */ + public void aboutToRun(IJobChangeEvent event); + + /** + * Notification that a job was previously sleeping and has now been rescheduled + * to run. + * + * @param event the event details + */ + public void awake(IJobChangeEvent event); + + /** + * Notification that a job has completed execution, either due to cancelation, successful + * completion, or failure. The event status object indicates how the job finished, + * and the reason for failure, if applicable. + * + * @param event the event details + */ + public void done(IJobChangeEvent event); + + /** + * Notification that a job has started running. + * + * @param event the event details + */ + public void running(IJobChangeEvent event); + + /** + * Notification that a job is being added to the queue of scheduled jobs. + * The event details includes the scheduling delay before the job should start + * running. + * + * @param event the event details, including the job instance and the scheduling + * delay + */ + public void scheduled(IJobChangeEvent event); + + /** + * Notification that a job was waiting to run and has now been put in the + * sleeping state. + * + * @param event the event details + */ + public void sleeping(IJobChangeEvent event); +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java new file mode 100644 index 0000000000..3b72cda67a --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 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.runtime.jobs; + +import org.eclipse.core.runtime.*; + +/** + * This is a functional interface representation of {@link Job}, suitable + * for use in lambda expressions. + * + * @see Job#create(String, IJobFunction) + * @since 3.6 + */ +public interface IJobFunction { + /** + * 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 Job#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 Job#ASYNC_FINISH + * @see Job#done(IStatus) + */ + public IStatus run(IProgressMonitor monitor); + +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java new file mode 100644 index 0000000000..454bcef845 --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java @@ -0,0 +1,426 @@ +/******************************************************************************* + * 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.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +/** + * The job manager provides facilities for scheduling, querying, and maintaining jobs + * and locks. In particular, the job manager provides the following services: + *

                            + *
                          • Maintains a queue of jobs that are waiting to be run. Items can be added to + * the queue using the schedule method.
                          • + *
                          • Allows manipulation of groups of jobs called job families. Job families can + * be canceled, put to sleep, or woken up atomically. There is also a mechanism + * for querying the set of known jobs in a given family.
                          • + *
                          • Allows listeners to find out about progress on running jobs, and to find out + * when jobs have changed states.
                          • + *
                          • Provides a factory for creating lock objects. Lock objects are smart monitors + * that have strategies for avoiding deadlock.
                          • + *
                          • Provide feedback to a client that is waiting for a given job or family of jobs + * to complete.
                          • + *
                          + * + * @see Job + * @see ILock + * @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 IJobManager { + /** + * A system property key indicating whether the job manager should create + * job threads as daemon threads. Set to true to force all worker + * threads to be created as daemon threads. Set to false to force + * all worker threads to be created as non-daemon threads. + * @since 3.3 + */ + public static final String PROP_USE_DAEMON_THREADS = "eclipse.jobs.daemon"; //$NON-NLS-1$ + + /** + * Registers a job listener with the job manager. + * Has no effect if an identical listener is already registered. + * + * @param listener the listener to be added + * @see #removeJobChangeListener(IJobChangeListener) + * @see IJobChangeListener + */ + public void addJobChangeListener(IJobChangeListener listener); + + /** + * Begins applying this rule in the calling thread. If the rule conflicts with another + * rule currently running in another thread, this method blocks until there are + * no conflicting rules. Calls to beginRule must eventually be followed + * by a matching call to endRule in the same thread and with the identical + * rule instance. + *

                          + * Rules can be nested only if the rule for the inner beginRule + * is contained within the rule for the outer beginRule. Rule containment + * is tested with the API method ISchedulingRule.contains. Also, begin/end + * pairs must be strictly nested. Only the rule that has most recently begun + * can be ended at any given time. + *

                          + * A rule of null can be used, but will be ignored for scheduling + * purposes. The outermost non-null rule in the thread will be used for scheduling. A + * null rule that is begun must still be ended. + *

                          + * If this method is called from within a job that has a scheduling rule, the + * given rule must also be contained within the rule for the running job. + *

                          + * Note that endRule must be called even if beginRule fails. + * The recommended usage is: + *

                          +	 * final ISchedulingRule rule = ...;
                          +	 * try {
                          +	 * 	manager.beginRule(rule, monitor);
                          +	 * } finally {
                          +	 * 	manager.endRule(rule);
                          +	 * }
                          +	 * 
                          + * + * @param rule the rule to begin applying in this thread, or null + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @throws IllegalArgumentException if the rule is not strictly nested within + * all other rules currently active for this thread + * @throws OperationCanceledException if the supplied monitor reports cancelation + * before the rule becomes available + * @see ISchedulingRule#contains(ISchedulingRule) + */ + public void beginRule(ISchedulingRule rule, IProgressMonitor monitor); + + /** + * Cancels all jobs in the given job family. Jobs in the family that are currently waiting + * 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. + * + * @param family the job family to cancel, or null to cancel all jobs + * @see Job#belongsTo(Object) + */ + public void cancel(Object family); + + /** + * Returns a progress monitor that can be used to provide + * aggregated progress feedback on a set of running jobs. A user + * interface will typically group all jobs in a progress group together, + * providing progress feedback for individual jobs as well as aggregated + * progress for the entire group. Jobs in the group may be run sequentially, + * in parallel, or some combination of the two. + *

                          + * Recommended usage (this snippet runs two jobs in sequence in a + * single progress group): + *

                          +	 *    Job parseJob, compileJob;
                          +	 *    IProgressMonitor pm = Platform.getJobManager().createProgressGroup();
                          +	 *    try {
                          +	 *       pm.beginTask("Building", 10);
                          +	 *       parseJob.setProgressGroup(pm, 5);
                          +	 *       parseJob.schedule();
                          +	 *       compileJob.setProgressGroup(pm, 5);
                          +	 *       compileJob.schedule();
                          +	 *       parseJob.join();
                          +	 *       compileJob.join();
                          +	 *    } finally {
                          +	 *       pm.done();
                          +	 *    }
                          +	 * 
                          + * + * @see Job#setProgressGroup(IProgressMonitor, int) + * @see IProgressMonitor + * @return a progress monitor + */ + public IProgressMonitor createProgressGroup(); + + /** + * Returns the scheduling rule currently held by this thread, or null + * if the current thread does not hold any scheduling rule. + *

                          + * If this method is called from within the scope of a running job with a non-null + * scheduling rule, then this method is equivalent to calling currentJob().getRule(). + * Otherwise, this method will return the first scheduling rule obtained by this + * thread via {@link #beginRule(ISchedulingRule, IProgressMonitor)} that has not + * yet had a corresponding call to {@link #endRule(ISchedulingRule)}. + *

                          + * + * @return the current rule or null + * @since 3.5 + */ + public ISchedulingRule currentRule(); + + /** + * Returns the job that is currently running in this thread, or null if there + * is no currently running job. + * + * @return the job or null + */ + public Job currentJob(); + + /** + * Ends the application of a rule to the calling thread. Calls to endRule + * must be preceded by a matching call to beginRule in the same thread + * with an identical rule instance. + *

                          + * Rules can be nested only if the rule for the inner beginRule + * is contained within the rule for the outer beginRule. Also, begin/end + * pairs must be strictly nested. Only the rule that has most recently begun + * can be ended at any given time. + * + * @param rule the rule to end applying in this thread + * @throws IllegalArgumentException if this method is called on a rule for which + * there is no matching begin, or that does not match the most recent begin. + * @see ISchedulingRule#contains(ISchedulingRule) + */ + public void endRule(ISchedulingRule rule); + + /** + * Returns all waiting, executing and sleeping jobs belonging + * to the given family. If no jobs are found, an empty array is returned. + * + * @param family the job family to find, or null to find all jobs + * @return the job array + * @see Job#belongsTo(Object) + */ + public Job[] find(Object family); + + /** + * Returns whether the job manager is currently idle. The job manager is + * idle if no jobs are currently running or waiting to run. + * + * @return true if the job manager is idle, and + * false otherwise + * @since 3.1 + */ + public boolean isIdle(); + + /** + * Returns whether the job manager is currently suspended. + * + * @return true if the job manager is suspended, and + * false otherwise + * @since 3.4 + * @see #suspend() + * @see #resume() + */ + public boolean isSuspended(); + + /** + * Waits until all jobs of the given family are finished. This method will block the + * calling thread until all such jobs have finished executing, or until this thread is + * interrupted. If there are no jobs in the family that are currently waiting, running, + * or sleeping, this method returns immediately. Feedback on how the join is + * progressing is provided to a 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 + * in the family in the {@link Job#RUNNING} state, this method returns. + *

                          + *

                          + * 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. This method can also result in starvation of the current thread if + * another thread continues to add jobs of the given family, or if a + * job in the given family reschedules itself in an infinite loop. + *

                          + * + * @param family the job family to join, or null to join all jobs. + * @param monitor Progress monitor for reporting progress on how the + * wait is progressing, or null if no progress monitoring is required. + * @exception InterruptedException if this thread is interrupted while waiting + * @exception OperationCanceledException if the progress monitor is canceled while waiting + * @see Job#belongsTo(Object) + * @see #suspend() + */ + public void join(Object family, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException; + + /** + * Creates a new lock object. All lock objects supplied by the job manager + * know about each other and will always avoid circular deadlock amongst + * themselves. + * + * @return the new lock object + */ + public ILock newLock(); + + /** + * Removes a job listener from the job manager. + * Has no effect if an identical listener is not already registered. + * + * @param listener the listener to be removed + * @see #addJobChangeListener(IJobChangeListener) + * @see IJobChangeListener + */ + public void removeJobChangeListener(IJobChangeListener listener); + + /** + * Resumes execution of jobs after a previous suspend. All + * jobs that were sleeping or waiting prior to the suspension, or that were + * scheduled while the job manager was suspended, will now be eligible + * for execution. + *

                          + * Calling this method on a rule that is not suspended has no effect. If another + * thread also owns the rule at the time this method is called, then the rule will + * not be resumed until all threads have released the rule. + * + * @deprecated This method is not safe and should not be used. + * Suspending a scheduling rule violates the thread safety + * of clients that use scheduling rules as a mutual exclusion mechanism, + * and can result in concurrency problems in all clients that use the suspended rule. + * @see #suspend(ISchedulingRule, IProgressMonitor) + */ + @Deprecated + public void resume(ISchedulingRule rule); + + /** + * Resumes execution of jobs after a previous suspend. All + * jobs that were sleeping or waiting prior to the suspension, or that were + * scheduled while the job manager was suspended, will now be eligible + * for execution. + *

                          + * Calling resume when the job manager is not suspended + * has no effect. + * + * @see #suspend() + * @see #isSuspended() + */ + public void resume(); + + /** + * Provides a hook that is notified whenever a thread is about to wait on a lock, + * or when a thread is about to release a lock. This hook must only be set once. + *

                          + * This method is for internal use by the platform-related plug-ins. + * Clients should not call this method. + *

                          + * @see LockListener + */ + public void setLockListener(LockListener listener); + + /** + * Registers a progress provider with the job manager. If there was a + * provider already registered, it is replaced. + *

                          + * This method is intended for use by the currently executing Eclipse application. + * Plug-ins outside the currently running application should not call this method. + *

                          + * + * @param provider the new provider, or null if no progress + * is needed + */ + public void setProgressProvider(ProgressProvider provider); + + /** + * Suspends execution of all jobs. Jobs that are already running + * when this method is invoked will complete as usual, but all sleeping and + * waiting jobs will not be executed until the job manager is resumed. + *

                          + * The job manager will remain suspended until a subsequent call to + * resume. Further calls to suspend + * when the job manager is already suspended are ignored. + *

                          + * All attempts to join sleeping and waiting jobs while the job manager is + * suspended will return immediately. + *

                          + * Note that this very powerful function should be used with extreme caution. + * Suspending the job manager will prevent all jobs in the system from executing, + * which may have adverse affects on components that are relying on + * execution of jobs. The job manager should never be suspended without intent + * to resume execution soon afterwards. + * + * @see #resume() + * @see #join(Object, IProgressMonitor) + * @see #isSuspended() + */ + public void suspend(); + + /** + * Defers execution of all jobs with scheduling rules that conflict with the + * given rule. The caller will be blocked until all currently executing jobs with + * conflicting rules are completed. Conflicting jobs that are sleeping or waiting at + * the time this method is called will not be executed until the rule is resumed. + *

                          + * While a rule is suspended, all calls to beginRule and + * endRule on a suspended rule will not block the caller. + * The rule remains suspended until a subsequent call to + * resume(ISchedulingRule) with the identical rule instance. + * Further calls to suspend with an identical rule prior to calling + * resume are ignored. + *

                          + *

                          + * This method is long-running; progress and cancelation are provided by + * the given progress monitor. In the case of cancelation, the rule will + * not be suspended. + *

                          + * Note: this very powerful function should be used with extreme caution. + * Suspending rules will prevent jobs in the system from executing, which may + * have adverse effects on components that are relying on execution of jobs. + * The job manager should never be suspended without intent to resume + * execution soon afterwards. Deadlock will result if the thread responsible + * for resuming the rule attempts to join a suspended job. + * + * @deprecated This method is not safe and should not be used. + * Suspending a scheduling rule violates the thread safety + * of clients that use scheduling rules as a mutual exclusion mechanism, + * and can result in concurrency problems in all clients that use the suspended rule. + * @param rule The scheduling rule to suspend. Must not be null. + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #resume(ISchedulingRule) + */ + @Deprecated + public void suspend(ISchedulingRule rule, IProgressMonitor monitor); + + /** + * Requests that all jobs in the given job family be suspended. Jobs currently + * waiting to be run will be removed from the queue and moved into the + * SLEEPING state. Jobs that have been put to sleep + * will remain in that state until either resumed or canceled. This method has + * no effect on jobs that are not currently waiting to be run. + *

                          + * Sleeping jobs can be resumed using wakeUp. + * + * @param family the job family to sleep, or null to sleep all jobs. + * @see Job#belongsTo(Object) + */ + public void sleep(Object family); + + /** + * Transfers ownership of a scheduling rule to another thread. The identical + * scheduling rule must currently be owned by the calling thread as a result of + * a previous call to beginRule. The destination thread must + * not already own a scheduling rule. + *

                          + * Calling this method is equivalent to atomically calling endRule + * in the calling thread followed by an immediate beginRule in + * the destination thread. The destination thread is responsible for subsequently + * calling endRule when it is finished using the rule. + *

                          + * This method has no effect when the destination thread is the same as the + * calling thread. + * + * @param rule The scheduling rule to transfer + * @param destinationThread The new owner for the transferred rule. + * @since 3.1 + */ + public void transferRule(ISchedulingRule rule, Thread destinationThread); + + /** + * Resumes scheduling of all sleeping jobs in the given family. This method + * has no effect on jobs in the family that are not currently sleeping. + * + * @param family the job family to wake up, or null to wake up all jobs + * @see Job#belongsTo(Object) + */ + public void wakeUp(Object family); +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java new file mode 100644 index 0000000000..07cd46299f --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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.runtime.IStatus; + +/** + * Represents status relating to the execution of jobs. + * + * @see org.eclipse.core.runtime.IStatus + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IJobStatus extends IStatus { + /** + * Returns the job associated with this status. + * + * @return the job associated with this status + */ + public Job getJob(); +} diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java new file mode 100644 index 0000000000..d19276e6fd --- /dev/null +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * 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; + +/** + * A lock is used to control access to an exclusive resource. + *

                          + * Locks are reentrant. That is, they can be acquired multiple times by the same thread + * without releasing. Locks are only released when the number of successful acquires + * equals the number of successful releases. + *

                          + * Locks are capable of detecting and recovering from programming errors that cause + * circular waiting deadlocks. When a deadlock between two or more ILock + * instances is detected, detailed debugging information is printed to the log file. The + * locks will then automatically recover from the deadlock by employing a release + * and wait strategy. One thread will lose control of the locks it owns, thus breaking + * the deadlock and allowing other threads to proceed. Once that thread's locks are + * all available, it will be given exclusive access to all its locks and allowed to proceed. + * A thread can only lose locks while it is waiting on an acquire() call. + * + *

                          + * Successive acquire attempts by different threads are queued and serviced on + * a first come, first served basis. + *

                          + * It is very important that acquired locks eventually get released. Calls to release + * should be done in a finally block to ensure they execute. For example: + *

                          + * try {
                          + * 	lock.acquire();
                          + * 	// ... do work here ...
                          + * } finally {
                          + * 	lock.release();
                          + * }
                          + * 
                          + * Note: although lock.acquire should never fail, it is good practice to place + * it inside the try block anyway. Releasing without acquiring is far less catastrophic + * than acquiring without releasing. + *

                          + * + * @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. +

                          +@since 3.0 +

                          + + diff --git a/third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF b/third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF index 22dfe134b5..3da79adb10 100644 --- a/third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF +++ b/third_party/patches/mars/org.eclipse.core.resources/META-INF/MANIFEST.MF @@ -28,6 +28,6 @@ Export-Package: org.eclipse.core.internal.dtree;x-internal:=true, org.eclipse.core.resources.refresh, org.eclipse.core.resources.team, org.eclipse.core.resources.variableresolvers -Bundle-Name: %pluginName +Bundle-Name: org.eclipse.core.resources patched for bug Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin diff --git a/third_party/patches/mars/pom.xml b/third_party/patches/mars/pom.xml index f4d6419657..cdd82be451 100644 --- a/third_party/patches/mars/pom.xml +++ b/third_party/patches/mars/pom.xml @@ -16,6 +16,7 @@ Patches bundles for Eclipse Mars - org.eclipse.core.resources + + org.eclipse.core.jobs diff --git a/third_party/patches/neon/org.eclipse.core.jobs/.classpath b/third_party/patches/neon/org.eclipse.core.jobs/.classpath new file mode 100644 index 0000000000..e8ea977a69 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/third_party/patches/neon/org.eclipse.core.jobs/.options b/third_party/patches/neon/org.eclipse.core.jobs/.options new file mode 100644 index 0000000000..801e8226eb --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/.project b/third_party/patches/neon/org.eclipse.core.jobs/.project new file mode 100644 index 0000000000..eac5e6eeee --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/.settings/org.eclipse.core.resources.prefs b/third_party/patches/neon/org.eclipse.core.jobs/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000000..16532b29ee --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/.settings/org.eclipse.core.runtime.prefs b/third_party/patches/neon/org.eclipse.core.jobs/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000000..5a0ad22d2a --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs b/third_party/patches/neon/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..8f1c687aa6 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,403 @@ +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.nonnull.secondary= +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullable.secondary= +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.nonnullTypeVariableFromLegacyInvocation=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.pessimisticNullAnalysisForFreeTypeVariables=warning +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=error +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/neon/org.eclipse.core.jobs/.settings/org.eclipse.jdt.ui.prefs b/third_party/patches/neon/org.eclipse.core.jobs/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000000..e8333bda65 --- /dev/null +++ b/third_party/patches/neon/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= +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/neon/org.eclipse.core.jobs/META-INF/MANIFEST.MF b/third_party/patches/neon/org.eclipse.core.jobs/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..df418f3df1 --- /dev/null +++ b/third_party/patches/neon/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.8.0.v20160509-0411 +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.8.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/neon/org.eclipse.core.jobs/about.html b/third_party/patches/neon/org.eclipse.core.jobs/about.html new file mode 100644 index 0000000000..460233046e --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/build.properties b/third_party/patches/neon/org.eclipse.core.jobs/build.properties new file mode 100644 index 0000000000..87ec7df29b --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/forceQualifierUpdate.txt b/third_party/patches/neon/org.eclipse.core.jobs/forceQualifierUpdate.txt new file mode 100644 index 0000000000..56f1032a8a --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/plugin.properties b/third_party/patches/neon/org.eclipse.core.jobs/plugin.properties new file mode 100644 index 0000000000..834b35fd15 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/plugin.xml b/third_party/patches/neon/org.eclipse.core.jobs/plugin.xml new file mode 100644 index 0000000000..60fd36db0e --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/plugin.xml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Counter.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Deadlock.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/DeadlockDetector.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/DeadlockDetector.java new file mode 100644 index 0000000000..3572e0321c --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ImplicitJobs.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ImplicitJobs.java new file mode 100644 index 0000000000..31de7d3d34 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJob.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJob.java new file mode 100644 index 0000000000..167334cbff --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJobGroup.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJobGroup.java new file mode 100644 index 0000000000..a6f6439489 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalWorker.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java new file mode 100644 index 0000000000..9b6d157b7d --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * 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 + for (IJobChangeListener listener : global) { + try { + doit.notify(listener, event); + } catch (Throwable e) { + handleException(listener, e); + } + } + for (IJobChangeListener listener : ((InternalJob) event.getJob()).getListeners()) { + try { + doit.notify(listener, event); + } catch (Throwable e) { + handleException(listener, e); + } + } + } + + private void handleException(IJobChangeListener 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/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java new file mode 100644 index 0000000000..b42fe112d8 --- /dev/null +++ b/third_party/patches/neon/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 monitorStack = new ArrayList<>(); + + private final InternalWorker internalWorker; + + public static void debug(String msg) { + StringBuffer msgBuf = new StringBuffer(msg.length() + 40); + if (DEBUG_TIMING) { + //lazy initialize to avoid overhead when not debugging + if (DEBUG_FORMAT == null) + DEBUG_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"); //$NON-NLS-1$ + DEBUG_FORMAT.format(new Date(), msgBuf, new FieldPosition(0)); + msgBuf.append('-'); + } + msgBuf.append('[').append(Thread.currentThread()).append(']').append(msg); + System.out.println(msgBuf.toString()); + } + + /** + * Returns the job manager singleton. For internal use only. + */ + static synchronized JobManager getInstance() { + if (instance == null) + new JobManager(); + return instance; + } + + /** + * For debugging purposes only + */ + private static String printJobName(Job job) { + if (job instanceof ThreadJob) { + Job realJob = ((ThreadJob) job).realJob; + if (realJob != null) + return realJob.getClass().getName(); + return "ThreadJob on rule: " + job.getRule(); //$NON-NLS-1$ + } + return job.getClass().getName(); + } + + /** + * For debugging purposes only + */ + public static String printState(Job job) { + return printState(((InternalJob) job).internalGetState()); + } + + /** + * For debugging purposes only + */ + public static String printState(int state) { + switch (state) { + case Job.NONE : + return "NONE"; //$NON-NLS-1$ + case Job.WAITING : + return "WAITING"; //$NON-NLS-1$ + case Job.SLEEPING : + return "SLEEPING"; //$NON-NLS-1$ + case Job.RUNNING : + return "RUNNING"; //$NON-NLS-1$ + case InternalJob.BLOCKED : + return "BLOCKED"; //$NON-NLS-1$ + case InternalJob.YIELDING : + return "YIELDING"; //$NON-NLS-1$ + case InternalJob.ABOUT_TO_RUN : + return "ABOUT_TO_RUN"; //$NON-NLS-1$ + case InternalJob.ABOUT_TO_SCHEDULE : + return "ABOUT_TO_SCHEDULE";//$NON-NLS-1$ + } + return "UNKNOWN"; //$NON-NLS-1$ + } + + /** + * Note that although this method is not API, clients have historically used + * it to force jobs shutdown in cases where OSGi shutdown does not occur. + * For this reason, this method should be considered near-API and should not + * be changed if at all possible. + */ + public static void shutdown() { + if (instance != null) { + instance.doShutdown(); + instance = null; + } + } + + private JobManager() { + instance = this; + synchronized (lock) { + waiting = new JobQueue(false); + waitingThreadJobs = new JobQueue(false, false); + sleeping = new JobQueue(true); + running = new HashSet<>(10); + yielding = new HashSet<>(10); + pool = new WorkerPool(this); + } + pool.setDaemon(JobOSGiUtils.getDefault().useDaemonThreads()); + internalWorker = new InternalWorker(this); + internalWorker.setDaemon(JobOSGiUtils.getDefault().useDaemonThreads()); + internalWorker.start(); + jobListeners.add(jobGroupUpdater); + } + + @Override + public void addJobChangeListener(IJobChangeListener listener) { + jobListeners.add(listener); + } + + @Override + public void beginRule(ISchedulingRule rule, IProgressMonitor monitor) { + validateRule(rule); + implicitJobs.begin(rule, monitorFor(monitor), false); + } + + /** + * Cancels a job + */ + protected boolean cancel(InternalJob job) { + IProgressMonitor monitor = null; + boolean runCanceling = false; + synchronized (lock) { + switch (job.getState()) { + case Job.NONE : + return true; + case Job.RUNNING : + //cannot cancel a job that has already started (as opposed to ABOUT_TO_RUN) + if (job.internalGetState() == Job.RUNNING) { + monitor = job.getProgressMonitor(); + runCanceling = !job.isRunCanceled(); + if (runCanceling) + job.setRunCanceled(true); + break; + } + //signal that the job should be canceled before it gets a chance to run + job.setAboutToRunCanceled(true); + return false; + default : + changeState(job, Job.NONE); + } + } + //call monitor and canceling outside sync block + if (monitor != null) { + if (runCanceling) { + if (!monitor.isCanceled()) + monitor.setCanceled(true); + job.canceling(); + } + return false; + } + //only notify listeners if the job was waiting or sleeping + jobListeners.done((Job) job, Status.CANCEL_STATUS, false); + return true; + } + + @Override + public void cancel(Object family) { + //don't synchronize because cancel calls listeners + for (Iterator it = select(family).iterator(); it.hasNext();) + cancel(it.next()); + } + + void cancel(InternalJobGroup jobGroup) { + cancel(jobGroup, false); + } + + void cancel(InternalJobGroup jobGroup, boolean cancelDueToError) { + Assert.isLegal(jobGroup != null, "jobGroup should not be null"); //$NON-NLS-1$ + synchronized (lock) { + switch (jobGroup.getState()) { + case JobGroup.NONE : + return; + case JobGroup.CANCELING : + if (!cancelDueToError) { + // User cancellation takes precedence over the cancel due to error. + jobGroup.updateCancelingReason(cancelDueToError); + } + return; + default : + jobGroup.cancelAndNotify(cancelDueToError); + } + } + } + + /** + * Atomically updates the state of a job, adding or removing from the + * necessary queues or sets. + */ + private void changeState(InternalJob job, int newState) { + boolean blockedJobs = false; + synchronized (lock) { + int oldJobState; + synchronized (job.jobStateLock) { + job.jobStateLock.notifyAll(); + oldJobState = job.getState(); + int oldState = job.internalGetState(); + switch (oldState) { + case InternalJob.YIELDING : + yielding.remove(job); + case Job.NONE : + case InternalJob.ABOUT_TO_SCHEDULE : + break; + case InternalJob.BLOCKED : + //remove this job from the linked list of blocked jobs + job.remove(); + break; + case Job.WAITING : + try { + waiting.remove(job); + } catch (RuntimeException e) { + Assert.isLegal(false, "Tried to remove a job that wasn't in the queue"); //$NON-NLS-1$ + } + break; + case Job.SLEEPING : + try { + sleeping.remove(job); + } catch (RuntimeException e) { + Assert.isLegal(false, "Tried to remove a job that wasn't in the queue"); //$NON-NLS-1$ + } + break; + case Job.RUNNING : + case InternalJob.ABOUT_TO_RUN : + running.remove(job); + //add any blocked jobs back to the wait queue + InternalJob blocked = job.previous(); + job.remove(); + blockedJobs = blocked != null; + while (blocked != null) { + InternalJob previous = blocked.previous(); + changeState(blocked, Job.WAITING); + blocked = previous; + } + break; + default : + Assert.isLegal(false, "Invalid job state: " + job + ", state: " + oldState); //$NON-NLS-1$ //$NON-NLS-2$ + } + job.internalSetState(newState); + switch (newState) { + case Job.NONE : + job.setStartTime(InternalJob.T_NONE); + job.setWaitQueueStamp(InternalJob.T_NONE); + job.setRunCanceled(false); + case InternalJob.BLOCKED : + break; + case Job.WAITING : + waiting.enqueue(job); + break; + case Job.SLEEPING : + try { + sleeping.enqueue(job); + } catch (RuntimeException e) { + throw new RuntimeException("Error changing from state: " + oldState); //$NON-NLS-1$ + } + break; + case Job.RUNNING : + case InternalJob.ABOUT_TO_RUN : + // These flags must be reset in all cases, including resuming from yield + job.setStartTime(InternalJob.T_NONE); + job.setWaitQueueStamp(InternalJob.T_NONE); + running.add(job); + break; + case InternalJob.YIELDING : + yielding.add(job); + case InternalJob.ABOUT_TO_SCHEDULE : + break; + default : + Assert.isLegal(false, "Invalid job state: " + job + ", state: " + newState); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + InternalJobGroup jobGroup = job.getJobGroup(); + if (jobGroup != null) { + jobGroup.jobStateChanged(job, oldJobState, job.getState()); + } + } + + //notify queue outside sync block + if (blockedJobs) + pool.jobQueued(); + } + + /** + * Returns a new progress monitor for this job, belonging to the given + * progress group. Returns null if it is not a valid time to set the job's group. + */ + protected IProgressMonitor createMonitor(InternalJob job, IProgressMonitor group, int ticks) { + synchronized (lock) { + //group must be set before the job is scheduled + //this includes the ABOUT_TO_SCHEDULE state, during which it is still + //valid to set the progress monitor + if (job.getState() != Job.NONE) + return null; + IProgressMonitor monitor = null; + if (progressProvider != null) + monitor = progressProvider.createMonitor((Job) job, group, ticks); + if (monitor == null) + monitor = new NullProgressMonitor(); + return monitor; + } + } + + /** + * Returns a new progress monitor for this job. Never returns null. + * @GuardedBy("lock") + */ + private IProgressMonitor createMonitor(Job job) { + IProgressMonitor monitor = null; + if (progressProvider != null) + monitor = progressProvider.createMonitor(job); + if (monitor == null) + monitor = new NullProgressMonitor(); + return monitor; + } + + @Override + public IProgressMonitor createProgressGroup() { + if (progressProvider != null) + return progressProvider.createProgressGroup(); + return new NullProgressMonitor(); + } + + @Override + public Job currentJob() { + Thread current = Thread.currentThread(); + if (current instanceof Worker) + return ((Worker) current).currentJob(); + synchronized (lock) { + for (Iterator it = running.iterator(); it.hasNext();) { + Job job = (Job) it.next(); + if (job.getThread() == current) + return job; + } + } + return null; + } + + @Override + public ISchedulingRule currentRule() { + //check thread job first, because actual current job may have null rule + Job currentJob = implicitJobs.getThreadJob(Thread.currentThread()); + if (currentJob != null) + return currentJob.getRule(); + currentJob = currentJob(); + if (currentJob != null) + return currentJob.getRule(); + return null; + } + + /** + * Returns the delay in milliseconds that a job with a given priority can + * tolerate waiting. + */ + private long delayFor(int priority) { + //these values may need to be tweaked based on machine speed + switch (priority) { + case Job.INTERACTIVE : + return 0L; + case Job.SHORT : + return 50L; + case Job.LONG : + return 100L; + case Job.BUILD : + return 500L; + case Job.DECORATE : + return 1000L; + default : + Assert.isTrue(false, "Job has invalid priority: " + priority); //$NON-NLS-1$ + return 0; + } + } + + /** + * Performs the scheduling of a job. Does not perform any notifications. + */ + private void doSchedule(InternalJob job, long delay) { + synchronized (lock) { + //job may have been canceled already + int state = job.internalGetState(); + if (state != InternalJob.ABOUT_TO_SCHEDULE && state != Job.SLEEPING) + return; + //if it's a decoration job with no rule, don't run it right now if the system is busy + if (job.getPriority() == Job.DECORATE && job.getRule() == null) { + long minDelay = running.size() * 100; + delay = Math.max(delay, minDelay); + } + if (delay > 0) { + job.setStartTime(System.currentTimeMillis() + delay); + changeState(job, Job.SLEEPING); + } else { + job.setStartTime(System.currentTimeMillis() + delayFor(job.getPriority())); + job.setWaitQueueStamp(waitQueueCounter.increment()); + changeState(job, Job.WAITING); + } + } + } + + /** + * Shuts down the job manager. Currently running jobs will be told + * to stop, but worker threads may still continue processing. + * (note: This implemented IJobManager.shutdown which was removed + * due to problems caused by premature shutdown) + */ + private void doShutdown() { + Job[] toCancel = null; + synchronized (lock) { + if (!active) + return; + active = false; + //cancel all running jobs + toCancel = running.toArray(new Job[running.size()]); + //discard any jobs that have not yet started running + sleeping.clear(); + waiting.clear(); + } + + // Give running jobs a chance to finish. Wait 0.1 seconds for up to 3 times. + if (toCancel != null && toCancel.length > 0) { + for (int i = 0; i < toCancel.length; i++) { + cancel(toCancel[i]); // cancel jobs outside sync block to avoid deadlock + } + + for (int waitAttempts = 0; waitAttempts < 3; waitAttempts++) { + Thread.yield(); + synchronized (lock) { + if (running.isEmpty()) + break; + } + if (DEBUG_SHUTDOWN) { + JobManager.debug("Shutdown - job wait cycle #" + (waitAttempts + 1)); //$NON-NLS-1$ + Job[] stillRunning = null; + synchronized (lock) { + stillRunning = running.toArray(new Job[running.size()]); + } + if (stillRunning != null) { + for (int j = 0; j < stillRunning.length; j++) { + JobManager.debug("\tJob: " + printJobName(stillRunning[j])); //$NON-NLS-1$ + } + } + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + //ignore + } + Thread.yield(); + } + + synchronized (lock) { // retrieve list of the jobs that are still running + toCancel = running.toArray(new Job[running.size()]); + } + } + internalWorker.cancel(); + if (toCancel != null) { + for (int i = 0; i < toCancel.length; i++) { + String jobName = printJobName(toCancel[i]); + //this doesn't need to be translated because it's just being logged + String msg = "Job found still running after platform shutdown. Jobs should be canceled by the plugin that scheduled them during shutdown: " + jobName; //$NON-NLS-1$ + RuntimeLog.log(new Status(IStatus.WARNING, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, null)); + + // TODO the RuntimeLog.log in its current implementation won't produce a log + // during this stage of shutdown. For now add a standard error output. + // One the logging story is improved, the System.err output below can be removed: + System.err.println(msg); + } + } + synchronized (lock) { + //discard reference to any jobs still running at this point + running.clear(); + } + + pool.shutdown(); + jobListeners.remove(jobGroupUpdater); + } + + /** + * Indicates that a job was running, and has now finished. Note that this method + * can be called under OutOfMemoryError conditions and thus must be paranoid + * about allocating objects. + */ + protected void endJob(InternalJob job, IStatus result, boolean notify) { + long rescheduleDelay = InternalJob.T_NONE; + synchronized (lock) { + //if the job is finishing asynchronously, there is nothing more to do for now + if (result == Job.ASYNC_FINISH) + return; + //if job is not known then it cannot be done + if (job.getState() == Job.NONE) + return; + if (JobManager.DEBUG && notify) + JobManager.debug("Ending job: " + job); //$NON-NLS-1$ + job.setResult(result); + job.setProgressMonitor(null); + job.setThread(null); + rescheduleDelay = job.getStartTime(); + changeState(job, Job.NONE); + } + //notify listeners outside sync block + final boolean reschedule = active && rescheduleDelay > InternalJob.T_NONE && job.shouldSchedule(); + if (notify) + jobListeners.done((Job) job, result, reschedule); + //reschedule the job if requested and we are still active + if (reschedule) + schedule(job, rescheduleDelay, reschedule); + //log result if it is warning or error. When the job belongs to a job group defer the logging + //until the whole group is completed (see JobManager#updateJobGroup). + if (job.getJobGroup() == null && result.matches(IStatus.ERROR | IStatus.WARNING)) + RuntimeLog.log(result); + } + + @Override + public void endRule(ISchedulingRule rule) { + implicitJobs.end(rule, false); + } + + @Override + public Job[] find(Object family) { + List members = select(family); + return members.toArray(new Job[members.size()]); + } + + List find(InternalJobGroup jobGroup) { + Assert.isLegal(jobGroup != null, "jobGroup should not be null"); //$NON-NLS-1$ + synchronized (lock) { + return jobGroup.internalGetActiveJobs(); + } + } + + /** + * Returns a running or blocked job whose scheduling rule conflicts with the + * scheduling rule of the given waiting job. Returns null if there are no + * conflicting jobs. A job can only run if there are no running jobs and no blocked + * jobs whose scheduling rule conflicts with its rule. + */ + protected InternalJob findBlockingJob(InternalJob waitingJob) { + if (waitingJob.getRule() == null) + return null; + synchronized (lock) { + if (running.isEmpty()) + return null; + //check the running jobs + boolean hasBlockedJobs = false; + for (Iterator it = running.iterator(); it.hasNext();) { + InternalJob job = it.next(); + if (waitingJob.isConflicting(job)) + return job; + if (!hasBlockedJobs) + hasBlockedJobs = job.previous() != null; + } + //there are no blocked jobs, so we are done + if (!hasBlockedJobs) + return null; + //check all jobs blocked by running jobs + for (Iterator it = running.iterator(); it.hasNext();) { + InternalJob job = it.next(); + while (true) { + job = job.previous(); + if (job == null) + break; + if (waitingJob.isConflicting(job)) + return job; + } + } + } + return null; + } + + /** + * Returns a job from the given collection whose scheduling rule conflicts + * with the scheduling rule of the given job. Returns null if there are no + * conflicting jobs. + */ + InternalJob findBlockedJob(InternalJob job, Iterator jobs) { + synchronized (lock) { + while (jobs.hasNext()) { + InternalJob waitingJob = (InternalJob) jobs.next(); + if (waitingJob.isConflicting(job)) + return waitingJob; + } + return null; + } + } + + void dequeue(JobQueue queue, InternalJob job) { + synchronized (lock) { + queue.remove(job); + } + } + + void enqueue(JobQueue queue, InternalJob job) { + synchronized (lock) { + queue.enqueue(job); + } + } + + public LockManager getLockManager() { + return lockManager; + } + + /** + * Returns a translated message indicating we are waiting for the given + * number of jobs to complete. + */ + private String getWaitMessage(int jobCount) { + String message = jobCount == 1 ? JobMessages.jobs_waitFamSubOne : JobMessages.jobs_waitFamSub; + return NLS.bind(message, Integer.toString(jobCount)); + } + + /** + * Returns whether the job manager is active (has not been shutdown). + */ + protected boolean isActive() { + return active; + } + + /** + * Returns true if the given job is blocking the execution of a non-system + * job. + */ + protected boolean isBlocking(InternalJob runningJob) { + synchronized (lock) { + // if this job isn't running, it can't be blocking anyone + if (runningJob.getState() != Job.RUNNING) + return false; + // if any job is queued behind this one, it is blocked by it + InternalJob previous = runningJob.previous(); + while (previous != null) { + // ignore jobs of lower priority (higher priority value means lower priority) + if (previous.getPriority() < runningJob.getPriority()) { + if (!previous.isSystem()) + return true; + // implicit jobs should interrupt unless they act on behalf of system jobs + if (previous instanceof ThreadJob && ((ThreadJob) previous).shouldInterrupt()) + return true; + } + previous = previous.previous(); + } + // consider threads waiting on IJobManager#beginRule + for (Iterator i = waitingThreadJobs.iterator(); i.hasNext();) { + ThreadJob waitingJob = (ThreadJob) i.next(); + if (runningJob.isConflicting(waitingJob) && waitingJob.shouldInterrupt()) + return true; + } + // none found + return false; + } + } + + @Override + public boolean isIdle() { + synchronized (lock) { + return running.isEmpty() && waiting.isEmpty(); + } + } + + @Override + public boolean isSuspended() { + synchronized (lock) { + return suspended; + } + } + + protected boolean join(InternalJob job, long timeout, IProgressMonitor monitor) throws InterruptedException { + Assert.isLegal(timeout >= 0, "timeout should not be negative"); //$NON-NLS-1$ + long deadline = timeout == 0 ? 0 : System.currentTimeMillis() + timeout; + + Job currentJob = currentJob(); + if (currentJob != null) { + JobGroup jobGroup = currentJob.getJobGroup(); + if (timeout == 0 && jobGroup != null && jobGroup.getMaxThreads() != 0 && jobGroup == job.getJobGroup()) + throw new IllegalStateException("Joining on a job belonging to the same group is not allowed"); //$NON-NLS-1$ + } + + final IJobChangeListener listener; + final Semaphore barrier; + synchronized (lock) { + int state = job.getState(); + if (state == Job.NONE) + return true; + //don't join a waiting or sleeping job when suspended (deadlock risk) + if (suspended && state != Job.RUNNING) + return true; + //it's an error for a job to join itself + if (state == Job.RUNNING && job.getThread() == Thread.currentThread()) + throw new IllegalStateException("Job attempted to join itself"); //$NON-NLS-1$ + //the semaphore will be released when the job is done + barrier = new Semaphore(null); + listener = new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + barrier.release(); + } + }; + job.addJobChangeListener(listener); + } + + //wait until listener notifies this thread. + try { + boolean canBlock = lockManager.canBlock(); + while (true) { + if (monitor != null && monitor.isCanceled()) + throw new OperationCanceledException(); + long remainingTime = deadline; + if (deadline != 0) { + remainingTime -= System.currentTimeMillis(); + if (remainingTime <= 0) { + return false; + } + } + //notify hook to service pending syncExecs before falling asleep + lockManager.aboutToWait(job.getThread()); + try { + // 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; + if (barrier.acquire(sleepTime)) + break; + } catch (InterruptedException e) { + // if non-UI thread, re-throw the exception + if (canBlock) + throw e; + // if UI thread, loop and keep trying + } + } + } finally { + lockManager.aboutToRelease(); + job.removeJobChangeListener(listener); + } + return true; + } + + @Override + public void join(final Object family, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException { + monitor = monitorFor(monitor); + IJobChangeListener listener = null; + final Set jobs; + int jobCount; + Job blocking = null; + synchronized (lock) { + //don't join a waiting or sleeping job when suspended (deadlock risk) + int states = suspended ? Job.RUNNING : Job.RUNNING | Job.WAITING | Job.SLEEPING; + jobs = Collections.synchronizedSet(new HashSet<>(select(family, states))); + jobCount = jobs.size(); + if (jobCount > 0) { + //if there is only one blocking job, use it in the blockage callback below + if (jobCount == 1) + blocking = (Job) jobs.iterator().next(); + listener = new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + //don't remove from list if job is being rescheduled + if (!((JobChangeEvent) event).reschedule) + jobs.remove(event.getJob()); + } + + //update the list of jobs if new ones are started during the join + @Override + public void running(IJobChangeEvent event) { + Job job = event.getJob(); + if (family == null || job.belongsTo(family)) + jobs.add(job); + } + + //update the list of jobs if new ones are scheduled during the join + @Override + public void scheduled(IJobChangeEvent event) { + //don't add to list if job is being rescheduled + if (((JobChangeEvent) event).reschedule) + return; + //if job manager is suspended we only wait for running jobs + if (isSuspended()) + return; + Job job = event.getJob(); + if (family == null || job.belongsTo(family)) + jobs.add(job); + } + }; + addJobChangeListener(listener); + } + } + if (jobCount == 0) { + //use up the monitor outside synchronized block because monitors call untrusted code + monitor.beginTask(JobMessages.jobs_blocked0, 1); + monitor.done(); + return; + } + //spin until all jobs are completed + try { + monitor.beginTask(JobMessages.jobs_blocked0, jobCount); + monitor.subTask(getWaitMessage(jobCount)); + reportBlocked(monitor, blocking); + int jobsLeft; + int reportedWorkDone = 0; + while ((jobsLeft = jobs.size()) > 0) { + //don't let there be negative work done if new jobs have + //been added since the join began + int actualWorkDone = Math.max(0, jobCount - jobsLeft); + if (reportedWorkDone < actualWorkDone) { + monitor.worked(actualWorkDone - reportedWorkDone); + reportedWorkDone = actualWorkDone; + monitor.subTask(getWaitMessage(jobsLeft)); + } + if (Thread.interrupted()) + throw new InterruptedException(); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + //notify hook to service pending syncExecs before falling asleep + lockManager.aboutToWait(null); + Thread.sleep(100); + } + } finally { + lockManager.aboutToRelease(); + removeJobChangeListener(listener); + reportUnblocked(monitor); + monitor.done(); + } + } + + boolean join(InternalJobGroup jobGroup, long timeout, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException { + Assert.isLegal(jobGroup != null, "jobGroup should not be null"); //$NON-NLS-1$ + Assert.isLegal(timeout >= 0, "timeout should not be negative"); //$NON-NLS-1$ + long deadline = timeout == 0 ? 0 : System.currentTimeMillis() + timeout; + int jobCount; + synchronized (lock) { + jobCount = jobGroup.getActiveJobsCount(); + } + + SubMonitor subMonitor = SubMonitor.convert(monitor, JobMessages.jobs_blocked0, jobCount); + try { + int jobsLeft; + while (true) { + if (subMonitor.isCanceled()) + throw new OperationCanceledException(); + long remainingTime = deadline; + if (deadline != 0) { + remainingTime -= System.currentTimeMillis(); + if (remainingTime <= 0) { + return false; + } + } + synchronized (lock) { + if ((suspended && jobGroup.getRunningJobsCount() == 0)) + break; + } + if (jobGroup.doJoin(remainingTime)) + break; + synchronized (lock) { + jobsLeft = jobGroup.getActiveJobsCount(); + } + if (jobsLeft < jobCount) { + subMonitor.worked(jobCount - jobsLeft); + } else { + jobCount = jobsLeft; + subMonitor.setWorkRemaining(jobCount); + } + subMonitor.subTask(getWaitMessage(jobsLeft)); + } + } finally { + if (monitor != null) { + monitor.done(); + } + } + return true; + } + + /** + * Returns a non-null progress monitor instance. If the monitor is null, + * returns the default monitor supplied by the progress provider, or a + * NullProgressMonitor if no default monitor is available. + */ + private IProgressMonitor monitorFor(IProgressMonitor monitor) { + if (monitor == null || (monitor instanceof NullProgressMonitor)) { + if (progressProvider != null) { + try { + monitor = progressProvider.getDefaultMonitor(); + } catch (Exception e) { + String msg = NLS.bind(JobMessages.meta_pluginProblems, JobManager.PI_JOBS); + RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, e)); + } + } + } + + if (monitor == null) + return new NullProgressMonitor(); + return monitor; + } + + @Override + public ILock newLock() { + return lockManager.newLock(); + } + + /** + * Removes and returns the first waiting job in the queue which is ready to run. + * Returns null if there are no items waiting in the queue. If an item is + * removed from the queue, it is moved to the running jobs list. + */ + private Job nextJob() { + synchronized (lock) { + // do nothing if the job manager is suspended + if (suspended) + return null; + // tickle the sleep queue to see if anyone wakes up + long now = System.currentTimeMillis(); + InternalJob job = sleeping.peek(); + while (job != null && job.getStartTime() < now) { + job.setStartTime(now + delayFor(job.getPriority())); + job.setWaitQueueStamp(waitQueueCounter.increment()); + changeState(job, Job.WAITING); + job = sleeping.peek(); + } + InternalJobGroup jobGroup = null; + // process the wait queue until we find a job whose rules are satisfied. + job = waiting.peek(); + while (job != null) { + InternalJob blocker = findBlockingJob(job); + jobGroup = job.getJobGroup(); + // previous() method returns the next job in the queue. + InternalJob nextWaitingJob = job.previous(); + if (blocker != null) { + // queue this job after the job that's blocking it + changeState(job, InternalJob.BLOCKED); + // assert job does not already belong to some other data structure + Assert.isTrue(job.next() == null); + Assert.isTrue(job.previous() == null); + blocker.addLast(job); + + } else if (jobGroup == null || jobGroup.getMaxThreads() == 0 || (jobGroup.getState() != JobGroup.CANCELING && jobGroup.getRunningJobsCount() < jobGroup.getMaxThreads())) { + break; + } + // skip this job as either this job is blocked on another job or + // the maximum number of jobs from the same group are already running. + job = nextWaitingJob == waiting.dummy ? null : nextWaitingJob; + } + // the job to run must be in the running list before we exit + // the sync block, otherwise two jobs with conflicting rules could start at once + if (job != null) { + changeState(job, InternalJob.ABOUT_TO_RUN); + if (JobManager.DEBUG) + JobManager.debug("Starting job: " + job); //$NON-NLS-1$ + } + return (Job) job; + } + } + + @Override + public void optionsChanged(DebugOptions options) { + DEBUG = options.getBooleanOption(OPTION_DEBUG_JOBS, false); + DEBUG_BEGIN_END = options.getBooleanOption(OPTION_DEBUG_BEGIN_END, false); + DEBUG_YIELDING = options.getBooleanOption(OPTION_DEBUG_YIELDING, false); + DEBUG_YIELDING_DETAILED = options.getBooleanOption(OPTION_DEBUG_YIELDING_DETAILED, false); + DEBUG_DEADLOCK = options.getBooleanOption(OPTION_DEADLOCK_ERROR, false); + DEBUG_LOCKS = options.getBooleanOption(OPTION_LOCKS, false); + DEBUG_TIMING = options.getBooleanOption(OPTION_DEBUG_JOBS_TIMING, false); + DEBUG_SHUTDOWN = options.getBooleanOption(OPTION_SHUTDOWN, false); + } + + @Override + public void removeJobChangeListener(IJobChangeListener listener) { + jobListeners.remove(listener); + } + + /** + * Report to the progress monitor that this thread is blocked, supplying + * an information message, and if possible the job that is causing the blockage. + * Important: An invocation of this method MUST be followed eventually be + * an invocation of reportUnblocked. + * @param monitor The monitor to report blocking to + * @param blockingJob The job that is blocking this thread, or null + * @see #reportUnblocked + */ + final void reportBlocked(IProgressMonitor monitor, InternalJob blockingJob) { + if (!(monitor instanceof IProgressMonitorWithBlocking)) + return; + IStatus reason; + if (blockingJob == null || blockingJob instanceof ThreadJob || blockingJob.isSystem()) { + reason = new Status(IStatus.INFO, JobManager.PI_JOBS, 1, JobMessages.jobs_blocked0, null); + } else { + String msg = NLS.bind(JobMessages.jobs_blocked1, blockingJob.getName()); + reason = new JobStatus(IStatus.INFO, (Job) blockingJob, msg); + } + ((IProgressMonitorWithBlocking) monitor).setBlocked(reason); + } + + /** + * Reports that this thread was blocked, but is no longer blocked and is able + * to proceed. + * @param monitor The monitor to report unblocking to. + * @see #reportBlocked + */ + final void reportUnblocked(IProgressMonitor monitor) { + if (monitor instanceof IProgressMonitorWithBlocking) + ((IProgressMonitorWithBlocking) monitor).clearBlocked(); + } + + @Override + public final void resume() { + synchronized (lock) { + suspended = false; + //poke the job pool + pool.jobQueued(); + } + } + + @Deprecated + @Override + public final void resume(ISchedulingRule rule) { + implicitJobs.resume(rule); + } + + /** + * Attempts to immediately start a given job. Returns null if the job was + * successfully started, and the blocking job if it could not be started immediately + * due to a currently running job with a conflicting rule. Listeners will never + * be notified of jobs that are run in this way. + */ + protected InternalJob runNow(ThreadJob job, boolean releaseWaiting) { + if (releaseWaiting) { + synchronized (implicitJobs) { + synchronized (lock) { + return doRunNow(job, releaseWaiting); + } + } + } + synchronized (lock) { + return doRunNow(job, releaseWaiting); + } + } + + private InternalJob doRunNow(ThreadJob job, boolean releaseWaiting) { + InternalJob blocking = findBlockingJob(job); + //cannot start if there is a conflicting job + if (blocking == null) { + changeState(job, Job.RUNNING); + ((InternalJob) job).setProgressMonitor(new NullProgressMonitor()); + job.run(null); + if (releaseWaiting) { + // atomically release waiting + implicitJobs.removeWaiting(job); + } + } + return blocking; + } + + protected void schedule(InternalJob job, long delay, boolean reschedule) { + if (!active) + throw new IllegalStateException("Job manager has been shut down."); //$NON-NLS-1$ + Assert.isNotNull(job, "Job is null"); //$NON-NLS-1$ + Assert.isLegal(delay >= 0, "Scheduling delay is negative"); //$NON-NLS-1$ + synchronized (lock) { + //if the job is already running, set it to be rescheduled when done + if (job.getState() == Job.RUNNING) { + job.setStartTime(delay); + return; + } + //can't schedule a job that is waiting or sleeping + if (job.internalGetState() != Job.NONE) + return; + if (JobManager.DEBUG) + JobManager.debug("Scheduling job: " + job); //$NON-NLS-1$ + //remember that we are about to schedule the job + //to prevent multiple schedule attempts from succeeding (bug 68452) + changeState(job, InternalJob.ABOUT_TO_SCHEDULE); + } + //notify listeners outside sync block + jobListeners.scheduled((Job) job, delay, reschedule); + //schedule the job + doSchedule(job, delay); + //call the pool outside sync block to avoid deadlock + pool.jobQueued(); + } + + /** + * Adds all family members in the list of jobs to the collection + */ + private void select(List members, Object family, InternalJob firstJob, int stateMask) { + if (firstJob == null) + return; + InternalJob job = firstJob; + do { + //note that job state cannot be NONE at this point + if ((family == null || job.belongsTo(family)) && ((job.getState() & stateMask) != 0)) + members.add(job); + job = job.previous(); + } while (job != null && job != firstJob); + } + + /** + * Returns a list of all jobs known to the job manager that belong to the given family. + */ + private List select(Object family) { + return select(family, Job.WAITING | Job.SLEEPING | Job.RUNNING); + } + + /** + * Returns a list of all jobs known to the job manager that belong to the given + * family and are in one of the provided states. + */ + private List select(Object family, int stateMask) { + List members = new ArrayList<>(); + synchronized (lock) { + if ((stateMask & Job.RUNNING) != 0) { + for (Iterator it = running.iterator(); it.hasNext();) { + select(members, family, it.next(), stateMask); + } + } + if ((stateMask & Job.WAITING) != 0) { + select(members, family, waiting.peek(), stateMask); + for (Iterator it = yielding.iterator(); it.hasNext();) { + select(members, family, it.next(), stateMask); + } + } + if ((stateMask & Job.SLEEPING) != 0) + select(members, family, sleeping.peek(), stateMask); + } + return members; + } + + @Override + public void setLockListener(LockListener listener) { + lockManager.setLockListener(listener); + } + + /** + * Changes a job priority. + */ + protected void setPriority(InternalJob job, int newPriority) { + synchronized (lock) { + int oldPriority = job.getPriority(); + if (oldPriority == newPriority) + return; + job.internalSetPriority(newPriority); + //if the job is waiting to run, re-shuffle the queue + if (job.getState() == Job.WAITING) { + long oldStart = job.getStartTime(); + job.setStartTime(oldStart + (delayFor(newPriority) - delayFor(oldPriority))); + waiting.resort(job); + } + } + } + + @Override + public void setProgressProvider(ProgressProvider provider) { + progressProvider = provider; + } + + public void setRule(InternalJob job, ISchedulingRule rule) { + synchronized (lock) { + //cannot change the rule of a job that is already running + Assert.isLegal(job.getState() == Job.NONE); + validateRule(rule); + job.internalSetRule(rule); + } + } + + /** + * Puts a job to sleep. Returns true if the job was successfully put to sleep. + */ + protected boolean sleep(InternalJob job) { + synchronized (lock) { + switch (job.getState()) { + case Job.RUNNING : + //cannot be paused if it is already running (as opposed to ABOUT_TO_RUN) + if (job.internalGetState() == Job.RUNNING) + return false; + //job hasn't started running yet (aboutToRun listener) + break; + case Job.SLEEPING : + //update the job wake time + job.setStartTime(InternalJob.T_INFINITE); + //change state again to re-shuffle the sleep queue + changeState(job, Job.SLEEPING); + return true; + case Job.NONE : + return true; + case Job.WAITING : + //put the job to sleep + break; + } + job.setStartTime(InternalJob.T_INFINITE); + changeState(job, Job.SLEEPING); + } + jobListeners.sleeping((Job) job); + return true; + } + + @Override + public void sleep(Object family) { + //don't synchronize because sleep calls listeners + for (Iterator it = select(family).iterator(); it.hasNext();) { + sleep(it.next()); + } + } + + /** + * Returns the estimated time in milliseconds before the next job is scheduled + * to wake up. The result may be negative. Returns InternalJob.T_INFINITE if + * there are no sleeping or waiting jobs. + */ + protected long sleepHint() { + synchronized (lock) { + //wait forever if job manager is suspended + if (suspended) + return InternalJob.T_INFINITE; + if (!waiting.isEmpty()) + return 0L; + //return the anticipated time that the next sleeping job will wake + InternalJob next = sleeping.peek(); + if (next == null) + return InternalJob.T_INFINITE; + return next.getStartTime() - System.currentTimeMillis(); + } + } + + /** + * Implementation of {@link Job#yieldRule(IProgressMonitor)} + */ + protected Job yieldRule(InternalJob job, IProgressMonitor monitor) { + Thread currentThread = Thread.currentThread(); + Assert.isLegal(job.getState() == Job.RUNNING, "Cannot yieldRule job that is " + printState(job.internalGetState())); //$NON-NLS-1$ + Assert.isLegal(currentThread == job.getThread(), "Cannot yieldRule from outside job's thread"); //$NON-NLS-1$ + + InternalJob unblocked; + // If job is not a ThreadJob, and it has implicitly started rules, likeThreadJob + // is the corresponding ThreadJob. Similarly, if likeThreadJob is not null, then + // job is not a ThreadJob + ThreadJob likeThreadJob; + synchronized (implicitJobs) { + synchronized (lock) { + // The nested implicit job, if any + likeThreadJob = implicitJobs.getThreadJob(currentThread); + + unblocked = job.previous(); + + // if unblocked is not null, it was a blocked job. It is guaranteed + // that it will be the next job run by the worker threads once this + // lock is released. + if (unblocked == null) { + + if (likeThreadJob != null) { + + // look for any explicit jobs we may be blocking + unblocked = ((InternalJob) likeThreadJob).previous(); + + if (unblocked == null) { + + // look for any implicit (or yielding) jobs we may be blocking. + unblocked = findBlockedJob(likeThreadJob, waitingThreadJobs.iterator()); + } + + } else { + + // look for any implicit (or yielding) jobs we may be blocking. + unblocked = findBlockedJob(job, waitingThreadJobs.iterator()); + } + } + + // optimization: do nothing if we don't unblock any job + if (unblocked == null) + return null; + + // "release" our rule by exiting RUNNING state + changeState(job, InternalJob.YIELDING); + if (DEBUG_YIELDING) + JobManager.debug(job + " will yieldRule to " + unblocked); //$NON-NLS-1$ + + if (likeThreadJob != null && likeThreadJob != job) { + // if there is a corresponding thread job, it needs yield as well + changeState(likeThreadJob, InternalJob.YIELDING); + if (DEBUG_YIELDING) + JobManager.debug(job + " will yieldRule to " + unblocked); //$NON-NLS-1$ + } + + if (likeThreadJob != null) { + // only null-out threads out for non-ThreadJobs + job.setThread(null); + if (likeThreadJob.getRule() != null) { + getLockManager().removeLockThread(currentThread, likeThreadJob.getRule()); + } + } + + if ((job.getRule() != null) && !(job instanceof ThreadJob)) + getLockManager().removeLockThread(currentThread, job.getRule()); + } + } + // To prevent this job from immediately re-grabbing the scheduling rule wait until + // the unblocked job changes state. This unblocked job is guaranteed to be the + // next job of the set of similar conflicting rules to attempt to run. + if (DEBUG_YIELDING_DETAILED) + JobManager.debug(job + " is waiting for " + unblocked + " to transition from WAITING state"); //$NON-NLS-1$ //$NON-NLS-2$ + + waitForUnblocked(unblocked); + + // restart this job, unless we've been restarted already + // This is the same as ThreadJob begin, except that cancelation CAN NOT be supported + // throwing the OperationCanceledException will return execution to the caller. + IProgressMonitor mon = monitorFor(monitor); + ProgressMonitorWrapper nonCanceling = new ProgressMonitorWrapper(mon) { + @Override + public boolean isCanceled() { + // pass-through request + getWrappedProgressMonitor().isCanceled(); + // ignore result + return false; + } + }; + + if (DEBUG_YIELDING) + JobManager.debug(job + " waiting to resume"); //$NON-NLS-1$ + + // this yielding job becomes an implicit job, unless it is one already + if (likeThreadJob == null) { + // Create a Threadjob proxy. This is strictly an internal job, but its not + // preventing from "leaking" out to clients in the form of listener + // notifications, and via IJobManager API usage like find(). + // Set a flag to differentiate it from regular ThreadJobs. + ThreadJob threadJob = new ThreadJob(job.getRule()) { + @Override + boolean isResumingAfterYield() { + return true; + } + }; + threadJob.setRealJob((Job) job); + ThreadJob.joinRun(threadJob, nonCanceling); + // the following state changes are atomic + synchronized (lock) { + // Must end the temporary threadJob to remove from running list + changeState(threadJob, Job.NONE); + changeState(job, Job.RUNNING); + job.setThread(currentThread); + } + } else { + ThreadJob.joinRun(likeThreadJob, nonCanceling); + synchronized (lock) { + changeState(job, Job.RUNNING); + job.setThread(currentThread); + } + } + if (DEBUG_YIELDING) { + // extra assert: make sure no other conflicting jobs are running now + synchronized (lock) { + for (Iterator it = running.iterator(); it.hasNext();) { + InternalJob other = it.next(); + if (other == job) + continue; + Assert.isTrue(!other.isConflicting(job), other + " conflicts and ran simultaneously with " + job); //$NON-NLS-1$ + } + } + JobManager.debug(job + " resumed"); //$NON-NLS-1$ + } + if (unblocked instanceof ThreadJob && ((ThreadJob) unblocked).isResumingAfterYield()) { + // if the unblocked job is a proxy for a yielding job to start, return + // the original job. No need to expose the proxy ThreadJob. + return ((ThreadJob) unblocked).realJob; + } + return (Job) unblocked; + } + + private void waitForUnblocked(InternalJob theJob) { + // wait until theJob leaves WAITING state + boolean interrupted = false; + synchronized (theJob.jobStateLock) { + if (theJob instanceof ThreadJob) { + // We can't acquire the implicitJob lock while holding jobStateLock, + // so use isWaiting instead. + while (((ThreadJob) theJob).isWaiting) { + try { + theJob.jobStateLock.wait(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } else { + while (theJob.internalGetState() == Job.WAITING) { + try { + theJob.jobStateLock.wait(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } + } + if (interrupted) + Thread.currentThread().interrupt(); + } + + /** + * Invokes {@link Job#shouldRun()} while guarding against unexpected failures. + */ + private boolean shouldRun(Job job) { + Throwable t; + try { + return job.shouldRun(); + } catch (Exception e) { + t = e; + } catch (LinkageError e) { + t = e; + } catch (AssertionError e) { + t = e; + } + RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "Error invoking shouldRun() method on: " + job, t)); //$NON-NLS-1$ + //if the should is unexpectedly failing it is safer not to run it + return false; + } + + /** + * Returns the next job to be run, or null if no jobs are waiting to run. + * The worker must call endJob when the job is finished running. + */ + protected Job startJob(Worker worker) { + Job job = null; + while (true) { + job = nextJob(); + if (job == null) + return null; + //must perform this outside sync block because it is third party code + boolean shouldRun = shouldRun(job); + //check for listener veto + if (shouldRun) + jobListeners.aboutToRun(job); + //listeners may have canceled or put the job to sleep + boolean endJob = false; + synchronized (lock) { + JobGroup jobGroup = job.getJobGroup(); + if (jobGroup != null && jobGroup.getState() == JobGroup.CANCELING) + shouldRun = false; + InternalJob internal = job; + synchronized (internal.jobStateLock) { + if (internal.internalGetState() == InternalJob.ABOUT_TO_RUN) { + if (shouldRun && !internal.isAboutToRunCanceled()) { + internal.setProgressMonitor(createMonitor(job)); + //change from ABOUT_TO_RUN to RUNNING + internal.setThread(worker); + internal.internalSetState(Job.RUNNING); + internal.jobStateLock.notifyAll(); + break; + } + internal.setAboutToRunCanceled(false); + endJob = true; + //fall through and end the job below + } + } + } + if (endJob) { + //job has been vetoed or canceled, so mark it as done + endJob(job, Status.CANCEL_STATUS, true); + continue; + } + } + jobListeners.running(job); + return job; + + } + + @Override + public final void suspend() { + synchronized (lock) { + suspended = true; + } + } + + @Deprecated + @Override + public final void suspend(ISchedulingRule rule, IProgressMonitor monitor) { + Assert.isNotNull(rule); + implicitJobs.suspend(rule, monitorFor(monitor)); + } + + @Override + public void transferRule(ISchedulingRule rule, Thread destinationThread) { + implicitJobs.transfer(rule, destinationThread); + } + + /** + * Validates that the given scheduling rule obeys the constraints of + * scheduling rules as described in the ISchedulingRule + * javadoc specification. + */ + private void validateRule(ISchedulingRule rule) { + //null rule always valid + if (rule == null) + return; + if (rule instanceof MultiRule) { + ISchedulingRule[] children = ((MultiRule) rule).getChildren(); + for (int i = 0; i < children.length; i++) { + Assert.isLegal(children[i] != rule); + validateRule(children[i]); + } + } + //contains method must be reflexive + Assert.isLegal(rule.contains(rule)); + //contains method must return false when given an unknown rule + Assert.isLegal(!rule.contains(nullRule)); + //isConflicting method must be reflexive + Assert.isLegal(rule.isConflicting(rule)); + //isConflicting method must return false when given an unknown rule + Assert.isLegal(!rule.isConflicting(nullRule)); + } + + protected void wakeUp(InternalJob job, long delay) { + Assert.isLegal(delay >= 0, "Scheduling delay is negative"); //$NON-NLS-1$ + synchronized (lock) { + //cannot wake up if it is not sleeping + if (job.getState() != Job.SLEEPING) + return; + doSchedule(job, delay); + } + //call the pool outside sync block to avoid deadlock + pool.jobQueued(); + + //only notify of wake up if immediate + if (delay == 0) + jobListeners.awake((Job) job); + } + + @Override + public void wakeUp(Object family) { + //don't synchronize because wakeUp calls listeners + for (Iterator it = select(family).iterator(); it.hasNext();) { + wakeUp(it.next(), 0L); + } + } + + void endMonitoring(ThreadJob threadJob) { + synchronized (monitorStack) { + for (int i = monitorStack.size() - 1; i >= 0; i--) { + if (monitorStack.get(i)[0] == threadJob) { + monitorStack.remove(i); + monitorStack.notifyAll(); + break; + } + } + } + } + + void beginMonitoring(ThreadJob threadJob, IProgressMonitor monitor) { + synchronized (monitorStack) { + monitorStack.add(new Object[] {threadJob, monitor}); + monitorStack.notifyAll(); + } + } + + /** + * Listens for the job completion events and checks for the job group cancellation, + * computes and logs the group result. + */ + private class JobGroupUpdater extends JobChangeAdapter { + Object jobManagerLock; + + public JobGroupUpdater(Object jobManagerLock) { + this.jobManagerLock = jobManagerLock; + } + + @Override + public void done(IJobChangeEvent event) { + InternalJob job = event.getJob(); + InternalJobGroup jobGroup = job.getJobGroup(); + if (jobGroup == null) + return; + IStatus jobResult = event.getResult(); + boolean reschedule = ((JobChangeEvent) event).reschedule; + + int jobGroupState; + int activeJobsCount; + int failedJobsCount; + int canceledJobsCount; + int seedJobsRemainingCount; + List jobResults = Collections.emptyList(); + synchronized (jobManagerLock) { + // Collect the required details to check for the group cancellation and completion + // outside the synchronized block. + jobGroupState = jobGroup.getState(); + activeJobsCount = jobGroup.getActiveJobsCount(); + failedJobsCount = jobGroup.getFailedJobsCount(); + canceledJobsCount = jobGroup.getCanceledJobsCount(); + seedJobsRemainingCount = jobGroup.getseedJobsRemainingCount(); + if (activeJobsCount == 0) + jobResults = jobGroup.getCompletedJobResults(); + } + + // Check for the group completion. + if (!reschedule && jobGroupState != JobGroup.NONE && activeJobsCount == 0 && (seedJobsRemainingCount <= 0 || jobGroupState == JobGroup.CANCELING)) { + // Must perform this outside the sync block to avoid a potential deadlock + MultiStatus jobGroupResult = jobGroup.computeGroupResult(jobResults); + Assert.isLegal(jobGroupResult != null, "The group result should not be null"); //$NON-NLS-1$ + boolean isJobGroupCompleted = false; + synchronized (jobManagerLock) { + // If more jobs were added to the group while were computing the result, the job group + // remains in the ACTIVE state and the computed result is discarded to be recomputed later, + // after the new jobs finish. + if (jobGroup.getState() != JobGroup.NONE && jobGroup.getActiveJobsCount() == 0) { + jobGroup.endJobGroup(jobGroupResult); + isJobGroupCompleted = true; + } + } + + // If the job group is completing, add the job group's status to the event + // and log errors and warnings. + if (isJobGroupCompleted) { + ((JobChangeEvent) event).jobGroupResult = jobGroupResult; + if (jobGroupResult.matches(IStatus.ERROR | IStatus.WARNING)) + RuntimeLog.log(jobGroupResult); + } + + return; + } + + if (jobGroupState != JobGroup.CANCELING && jobGroup.shouldCancel(jobResult, failedJobsCount, canceledJobsCount)) + cancel(jobGroup, true); + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java new file mode 100644 index 0000000000..48522c8bae --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java @@ -0,0 +1,52 @@ +/********************************************************************** + * 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 - Initial API and implementation + **********************************************************************/ +package org.eclipse.core.internal.jobs; + +import java.util.Date; +import org.eclipse.osgi.util.NLS; + +/** + * Job plugin message catalog + */ +public class JobMessages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.core.internal.jobs.messages"; //$NON-NLS-1$ + + // Job Manager and Locks + public static String jobs_blocked0; + public static String jobs_blocked1; + public static String jobs_internalError; + public static String jobs_waitFamSub; + public static String jobs_waitFamSubOne; + // metadata + public static String meta_pluginProblems; + + static { + // load message values from bundle file + reloadMessages(); + } + + public static void reloadMessages() { + NLS.initializeMessages(BUNDLE_NAME, JobMessages.class); + } + + /** + * Print a debug message to the console. + * Pre-pend the message with the current date and the name of the current thread. + */ + public static void message(String message) { + StringBuffer buffer = new StringBuffer(); + buffer.append(new Date(System.currentTimeMillis())); + buffer.append(" - ["); //$NON-NLS-1$ + buffer.append(Thread.currentThread().getName()); + buffer.append("] "); //$NON-NLS-1$ + buffer.append(message); + System.out.println(buffer.toString()); + } +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java new file mode 100644 index 0000000000..a4d588ab92 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * 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.jobs; + +import java.util.Hashtable; +import org.eclipse.core.runtime.jobs.IJobManager; +import org.eclipse.osgi.service.debug.DebugOptions; +import org.eclipse.osgi.service.debug.DebugOptionsListener; +import org.osgi.framework.*; +import org.osgi.service.packageadmin.PackageAdmin; +import org.osgi.util.tracker.ServiceTracker; + +/** + * The class contains a set of helper methods for the runtime Jobs plugin. + * The following utility methods are supplied: + * - provides access to debug options + * - provides some bundle discovery functionality + * + * The closeServices() method should be called before the plugin is stopped. + * + * @since org.eclipse.core.jobs 3.2 + */ +class JobOSGiUtils { + private ServiceRegistration debugRegistration = null; + private ServiceTracker bundleTracker = null; + + private static final JobOSGiUtils singleton = new JobOSGiUtils(); + + /** + * Accessor for the singleton instance + * @return The JobOSGiUtils instance + */ + public static JobOSGiUtils getDefault() { + return singleton; + } + + /** + * Private constructor to block instance creation. + */ + private JobOSGiUtils() { + super(); + } + + @SuppressWarnings("unchecked") + void openServices() { + BundleContext context = JobActivator.getContext(); + if (context == null) { + if (JobManager.DEBUG) + JobMessages.message("JobsOSGiUtils called before plugin started"); //$NON-NLS-1$ + return; + } + + // register debug options listener + Hashtable properties = new Hashtable<>(2); + properties.put(DebugOptions.LISTENER_SYMBOLICNAME, JobManager.PI_JOBS); + debugRegistration = context.registerService(DebugOptionsListener.class, JobManager.getInstance(), properties); + + bundleTracker = new ServiceTracker(context, PackageAdmin.class.getName(), null); + bundleTracker.open(); + } + + void closeServices() { + if (debugRegistration != null) { + debugRegistration.unregister(); + debugRegistration = null; + } + if (bundleTracker != null) { + bundleTracker.close(); + bundleTracker = null; + } + } + + /** + * Returns the bundle id of the bundle that contains the provided object, or + * null if the bundle could not be determined. + */ + public String getBundleId(Object object) { + if (bundleTracker == null) { + if (JobManager.DEBUG) + JobMessages.message("Bundle tracker is not set"); //$NON-NLS-1$ + return null; + } + PackageAdmin packageAdmin = (PackageAdmin) bundleTracker.getService(); + if (object == null) + return null; + if (packageAdmin == null) + return null; + Bundle source = packageAdmin.getBundle(object.getClass()); + if (source != null && source.getSymbolicName() != null) + return source.getSymbolicName(); + return null; + } + + /** + * Calculates whether the job plugin should set worker threads to be daemon + * threads. When workers are daemon threads, the job plugin does not need + * to be explicitly shut down because the VM can exit while workers are still + * alive. + * @return true if all worker threads should be daemon threads, + * and false otherwise. + */ + boolean useDaemonThreads() { + BundleContext context = JobActivator.getContext(); + if (context == null) { + //we are running stand-alone, so consult global system property + String value = System.getProperty(IJobManager.PROP_USE_DAEMON_THREADS); + //default to use daemon threads if property is absent + if (value == null) + return true; + return "true".equalsIgnoreCase(value); //$NON-NLS-1$ + } + //only use daemon threads if the property is defined + final String value = context.getProperty(IJobManager.PROP_USE_DAEMON_THREADS); + //if value is absent, don't use daemon threads to maintain legacy behaviour + if (value == null) + return false; + return "true".equalsIgnoreCase(value); //$NON-NLS-1$ + } +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java new file mode 100644 index 0000000000..356c643698 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * 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.Iterator; +import org.eclipse.core.runtime.*; + +/** + * A linked list based priority queue. + */ +public final class JobQueue { + /** + * The dummy entry sits between the head and the tail of the queue. + * dummy.previous() is the head, and dummy.next() is the tail. + */ + protected final InternalJob dummy; + + /** + * If true, conflicting jobs will be allowed to overtake others in the + * queue that have lower priority. If false, higher priority jumps can only + * move up the queue by overtaking jobs that they don't conflict with. + */ + private final boolean allowConflictOvertaking; + + private final boolean allowPriorityOvertaking; + + /** + * Create a new job queue. + */ + public JobQueue(boolean allowConflictOvertaking) { + this(allowConflictOvertaking, true); + } + + /** + * Create a new job queue. + */ + public JobQueue(boolean allowConflictOvertaking, boolean allowPriorityOvertaking) { + this.allowPriorityOvertaking = allowPriorityOvertaking; + //compareTo on dummy is never called + dummy = new InternalJob("Queue-Head") {//$NON-NLS-1$ + @Override + public IStatus run(IProgressMonitor m) { + return Status.OK_STATUS; + } + }; + dummy.setNext(dummy); + dummy.setPrevious(dummy); + this.allowConflictOvertaking = allowConflictOvertaking; + } + + /** + * remove all elements + */ + public void clear() { + dummy.setNext(dummy); + dummy.setPrevious(dummy); + } + + /** + * Return and remove the element with highest priority, or null if empty. + */ + public InternalJob dequeue() { + InternalJob toRemove = dummy.previous(); + if (toRemove == dummy) + return null; + return toRemove.remove(); + } + + /** + * Adds an item to the queue + */ + public void enqueue(InternalJob newEntry) { + //assert new entry is does not already belong to some other data structure + Assert.isTrue(newEntry.next() == null); + Assert.isTrue(newEntry.previous() == null); + InternalJob tail = dummy.next(); + //overtake lower priority jobs. Only overtake conflicting jobs if allowed to + while (canOvertake(newEntry, tail)) + tail = tail.next(); + //new entry is smaller than tail + final InternalJob tailPrevious = tail.previous(); + newEntry.setNext(tail); + newEntry.setPrevious(tailPrevious); + tailPrevious.setNext(newEntry); + tail.setPrevious(newEntry); + } + + /** + * Returns whether the new entry to overtake the existing queue entry. + * @param newEntry The entry to be added to the queue + * @param queueEntry The existing queue entry + */ + private boolean canOvertake(InternalJob newEntry, InternalJob queueEntry) { + //can never go past the end of the queue + if (queueEntry == dummy) + return false; + //if the new entry was already in the wait queue, ensure it is re-inserted in correct position (bug 211799) + if (newEntry.getWaitQueueStamp() > 0 && newEntry.getWaitQueueStamp() < queueEntry.getWaitQueueStamp()) + return true; + //if the new entry has lower priority, there is no need to overtake the existing entry + if (allowPriorityOvertaking && queueEntry.compareTo(newEntry) >= 0) + return false; + //the new entry has higher priority, but only overtake the existing entry if the queue allows it + return allowConflictOvertaking || !newEntry.isConflicting(queueEntry); + } + + /** + * Removes the given element from the queue. + */ + public void remove(InternalJob toRemove) { + toRemove.remove(); + //previous of toRemove might now bubble up + } + + /** + * The given object has changed priority. Reshuffle the heap until it is + * valid. + */ + public void resort(InternalJob entry) { + remove(entry); + enqueue(entry); + } + + /** + * Returns true if the queue is empty, and false otherwise. + */ + public boolean isEmpty() { + return dummy.next() == dummy; + } + + /** + * Return greatest element without removing it, or null if empty + */ + public InternalJob peek() { + return dummy.previous() == dummy ? null : dummy.previous(); + } + + public Iterator iterator() { + return new Iterator() { + InternalJob pointer = dummy; + + @Override + public boolean hasNext() { + if (pointer.previous() == dummy) + pointer = null; + else + pointer = pointer.previous(); + return pointer != null; + } + + @Override + public Object next() { + return pointer; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java new file mode 100644 index 0000000000..af4f97e62d --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * 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.internal.jobs; + +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.IJobStatus; +import org.eclipse.core.runtime.jobs.Job; + +/** + * Standard implementation of the IJobStatus interface. + */ +public class JobStatus extends Status implements IJobStatus { + private Job job; + + /** + * Creates a new job status with no interesting error code or exception. + * @param severity + * @param job + * @param message + */ + public JobStatus(int severity, Job job, String message) { + super(severity, JobManager.PI_JOBS, 1, message, null); + this.job = job; + } + + @Override + public Job getJob() { + return job; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java new file mode 100644 index 0000000000..b4ff94291e --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java @@ -0,0 +1,334 @@ +/******************************************************************************* + * 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 java.util.HashMap; +import java.util.Stack; +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.LockListener; + +/** + * Stores the only reference to the graph that contains all the known + * relationships between locks, rules, and the threads that own them. + * Synchronizes all access to the graph on the only instance that exists in this class. + * + * Also stores the state of suspended locks so that they can be re-acquired with + * the proper lock depth. + */ +public class LockManager { + /** + * This class captures the state of suspended locks. + * Locks are suspended if deadlock is detected. + */ + private static class LockState { + private int depth; + private OrderedLock lock; + + /** + * Suspends ownership of the given lock, and returns the saved state. + */ + protected static LockState suspend(OrderedLock lock) { + LockState state = new LockState(); + state.lock = lock; + state.depth = lock.forceRelease(); + return state; + } + + /** + * Re-acquires a suspended lock and reverts to the correct lock depth. + */ + public void resume() { + //spin until the lock is successfully acquired + //NOTE: spinning here allows the UI thread to service pending syncExecs + //if the UI thread is waiting to acquire a lock. + while (true) { + try { + if (lock.acquire(Long.MAX_VALUE)) + break; + } catch (InterruptedException e) { + //ignore and loop + } + } + lock.setDepth(depth); + } + } + + //the lock listener for this lock manager + protected LockListener lockListener; + /* + * The internal data structure that stores all the relationships + * between the locks (or rules) and the threads that own them. + */ + private DeadlockDetector locks = new DeadlockDetector(); + /* + * Stores thread - stack pairs where every entry in the stack is an array + * of locks that were suspended while the thread was acquiring more locks + * (a stack is needed because when a thread tries to re-acquire suspended locks, + * it can cause deadlock, and some locks it owns can be suspended again) + */ + private HashMap> suspendedLocks = new HashMap<>(); + + public LockManager() { + super(); + } + + public void aboutToRelease() { + if (lockListener == null) + return; + try { + lockListener.aboutToRelease(); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + } + + public boolean canBlock() { + if (lockListener == null) + return true; + try { + return lockListener.canBlock(); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + return false; + } + + public boolean aboutToWait(Thread lockOwner) { + if (lockListener == null) + return false; + try { + return lockListener.aboutToWait(lockOwner); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + return false; + } + + /** + * This thread has just acquired a lock. Update graph. + */ + void addLockThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockAcquired(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just been refused a lock. Update graph and check for deadlock. + */ + void addLockWaitThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + Deadlock found = null; + synchronized (tempLocks) { + try { + found = tempLocks.lockWaitStart(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + if (found == null) + return; + // if deadlock was detected, the found variable will contain all the information about it, + // including which locks to suspend for which thread to resolve the deadlock. + ISchedulingRule[] toSuspend = found.getLocks(); + LockState[] suspended = new LockState[toSuspend.length]; + for (int i = 0; i < toSuspend.length; i++) + suspended[i] = LockState.suspend((OrderedLock) toSuspend[i]); + synchronized (suspendedLocks) { + Stack prevLocks = suspendedLocks.get(found.getCandidate()); + if (prevLocks == null) + prevLocks = new Stack<>(); + prevLocks.push(suspended); + suspendedLocks.put(found.getCandidate(), prevLocks); + } + } catch (Exception e) { + handleInternalError(e); + } + } + + private Exception createDebugException(DeadlockDetector tempLocks, Exception rootException) { + String debugString = null; + try { + debugString = tempLocks.toDebugString(); + } catch (Exception e) { + //ignore failure to create the debug string + } + return new Exception(debugString, rootException); + } + + /** + * Handles exceptions that occur while calling third party code from within the + * LockManager. This is essentially an in-lined version of Platform.run(ISafeRunnable) + */ + private static void handleException(Throwable e) { + IStatus status; + if (e instanceof CoreException) { + //logged message should not be translated + status = new MultiStatus(JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "LockManager.handleException", e); //$NON-NLS-1$ + ((MultiStatus) status).merge(((CoreException) e).getStatus()); + } else { + status = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "LockManager.handleException", e); //$NON-NLS-1$ + } + RuntimeLog.log(status); + } + + /** + * There was an internal error in the deadlock detection code. Shut the entire + * thing down to prevent further errors. Recovery is too complex as it + * requires freezing all threads and inferring the present lock state. + */ + private void handleInternalError(Throwable t) { + try { + handleException(t); + } catch (Exception e) { + //ignore failure to log + } + //discard the deadlock detector for good + locks = null; + } + + /** + * Returns true IFF the underlying graph is empty. + * For debugging purposes only. + */ + public boolean isEmpty() { + return locks.isEmpty(); + } + + /** + * Returns true IFF this thread either owns, or is waiting for, any locks or rules. + */ + public boolean isLockOwner() { + //all job threads have to be treated as lock owners because UI thread + //may try to join a job + Thread current = Thread.currentThread(); + if (current instanceof Worker) + return true; + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return false; + synchronized (tempLocks) { + return tempLocks.contains(Thread.currentThread()); + } + } + + /** + * Creates and returns a new lock. + */ + public synchronized OrderedLock newLock() { + return new OrderedLock(this); + } + + /** + * Releases all the acquires that were called on the given rule. Needs to be called only once. + */ + void removeLockCompletely(Thread thread, ISchedulingRule rule) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockReleasedCompletely(thread, rule); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just released a lock. Update graph. + */ + void removeLockThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockReleased(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just stopped waiting for a lock. Update graph. + * If the thread has already been granted the lock (or wasn't waiting + * for the lock) then the graph remains unchanged. + */ + void removeLockWaitThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockWaitStop(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * Resumes all the locks that were suspended while this thread was waiting to acquire another lock. + */ + void resumeSuspendedLocks(Thread owner) { + LockState[] toResume; + synchronized (suspendedLocks) { + Stack prevLocks = suspendedLocks.get(owner); + if (prevLocks == null) + return; + toResume = (LockState[]) prevLocks.pop(); + if (prevLocks.empty()) + suspendedLocks.remove(owner); + } + for (int i = 0; i < toResume.length; i++) + toResume[i].resume(); + } + + public void setLockListener(LockListener listener) { + this.lockListener = listener; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java new file mode 100644 index 0000000000..1983c5c962 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java @@ -0,0 +1,311 @@ +/******************************************************************************* + * 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.internal.jobs; + +import java.util.*; + +/** + * A specialized map implementation that is optimized for a small set of object + * keys. + * + * Implemented as a single array that alternates keys and values. + * + * Note: This class is copied from org.eclipse.core.resources + */ +public class ObjectMap implements Map { + // 8 attribute keys, 8 attribute values + protected static final int DEFAULT_SIZE = 16; + protected static final int GROW_SIZE = 10; + protected int count = 0; + protected Object[] elements = null; + + /** + * Creates a new object map. + * + * @param initialCapacity + * The initial number of elements that will fit in the map. + */ + public ObjectMap(int initialCapacity) { + if (initialCapacity > 0) + elements = new Object[Math.max(initialCapacity * 2, 0)]; + } + + /** + * Creates a new object map of the same size as the given map and populate + * it with the key/attribute pairs found in the map. + * + * @param map + * The entries in the given map will be added to the new map. + */ + public ObjectMap(Map map) { + this(map.size()); + putAll(map); + } + + /** + * @see Map#clear() + */ + @Override + public void clear() { + elements = null; + count = 0; + } + + /** + * @see java.lang.Object#clone() + */ + @Override + public Object clone() { + return new ObjectMap(this); + } + + /** + * @see Map#containsKey(java.lang.Object) + */ + @Override + public boolean containsKey(Object key) { + if (elements == null || count == 0) + return false; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return true; + return false; + } + + /** + * @see Map#containsValue(java.lang.Object) + */ + @Override + public boolean containsValue(Object value) { + if (elements == null || 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; + } + + /** + * @see Map#entrySet() + * + * Note: 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 count == 0 ? Collections.EMPTY_SET : toHashMap().entrySet(); + } + + /** + * @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; + //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; + } + + /** + * @see Map#get(java.lang.Object) + */ + @Override + public Object get(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return elements[i + 1]; + return null; + } + + /** + * The capacity of the map has been exceeded, grow the array by GROW_SIZE to + * accommodate more entries. + */ + protected void grow() { + Object[] expanded = new Object[elements.length + GROW_SIZE]; + System.arraycopy(elements, 0, expanded, 0, elements.length); + elements = expanded; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 0; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + hash += elements[i].hashCode(); + } + } + return hash; + } + + /** + * @see Map#isEmpty() + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * @see Map#keySet() + * + * Note: 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()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.add(elements[i]); + } + } + return result; + } + + /** + * @see Map#put(java.lang.Object, java.lang.Object) + */ + @Override + public Object put(Object key, Object value) { + if (key == null) + throw new NullPointerException(); + if (value == null) + return remove(key); + + // handle the case where we don't have any attributes yet + if (elements == null) + elements = new Object[DEFAULT_SIZE]; + if (count == 0) { + elements[0] = key; + elements[1] = value; + count++; + return null; + } + + int emptyIndex = -1; + // replace existing value if it exists + for (int i = 0; i < elements.length; i += 2) { + if (elements[i] != null) { + if (elements[i].equals(key)) { + Object oldValue = elements[i + 1]; + elements[i + 1] = value; + return oldValue; + } + } else if (emptyIndex == -1) { + // keep track of the first empty index + emptyIndex = i; + } + } + // this will put the emptyIndex greater than the size but + // that's ok because we will grow first. + if (emptyIndex == -1) + emptyIndex = count * 2; + + // otherwise add it to the list of elements. + // grow if necessary + if (elements.length <= (count * 2)) + grow(); + elements[emptyIndex] = key; + elements[emptyIndex + 1] = value; + count++; + return null; + } + + /** + * @see Map#putAll(java.util.Map) + */ + @Override + public void putAll(Map map) { + for (Iterator i = map.keySet().iterator(); i.hasNext();) { + Object key = i.next(); + Object value = map.get(key); + put(key, value); + } + } + + /** + * @see Map#remove(java.lang.Object) + */ + @Override + public Object remove(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null && elements[i].equals(key)) { + elements[i] = null; + Object result = elements[i + 1]; + elements[i + 1] = null; + count--; + return result; + } + } + return null; + } + + /** + * @see Map#size() + */ + @Override + public int size() { + return count; + } + + /** + * Creates a new hash map with the same contents as this map. + */ + private HashMap toHashMap() { + HashMap result = new HashMap<>(size()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.put(elements[i], elements[i + 1]); + } + } + return result; + } + + /** + * @see Map#values() + * + * Note: 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()); + for (int i = 1; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.add(elements[i]); + } + } + return result; + } +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java new file mode 100644 index 0000000000..4744e198b6 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java @@ -0,0 +1,310 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - Bug 311863 Ordered Lock lost after interrupt + *******************************************************************************/ +package org.eclipse.core.internal.jobs; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.jobs.ILock; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * A lock used to control write access to an exclusive resource. + * + * The lock avoids circular waiting deadlocks by detecting the deadlocks + * and resolving them through the suspension of all locks owned by one + * of the threads involved in the deadlock. This makes it impossible for n such + * locks to deadlock while waiting for each other. The down side is that this means + * that during an interval when a process owns a lock, it can be forced + * to give the lock up and wait until all locks it requires become + * available. This removes the feature of exclusive access to the + * resource in contention for the duration between acquire() and + * release() calls. + * + * The lock implementation prevents starvation by granting the + * lock in the same order in which acquire() requests arrive. In + * this scheme, starvation is only possible if a thread retains + * a lock indefinitely. + */ +public class OrderedLock implements ILock, ISchedulingRule { + + private static final boolean DEBUG = false; + /** + * Locks are sequentially ordered for debugging purposes. + */ + private static int nextLockNumber = 0; + /** + * The thread of the operation that currently owns the lock. + */ + private volatile Thread currentOperationThread; + /** + * Records the number of successive acquires in the same + * thread. The lock is released only when the depth + * reaches zero. + */ + private int depth; + /** + * The manager that implements the deadlock detection and resolution protocol. + */ + private final LockManager manager; + private final int number; + + /** + * Queue of semaphores for threads currently waiting + * on the lock. This queue is not thread-safe, so access + * to this queue must be synchronized on the lock instance. + */ + private final Queue operations = new Queue(); + + /** + * Creates a new workspace lock. + */ + OrderedLock(LockManager manager) { + this.manager = manager; + this.number = nextLockNumber++; + } + + @Override + public void acquire() { + //spin until the lock is successfully acquired + //NOTE: spinning here allows the UI thread to service pending syncExecs + //if the UI thread is waiting to acquire a lock. + boolean interrupted = false; + while (true) { + try { + if (acquire(Long.MAX_VALUE)) + break; + } catch (InterruptedException e) { + interrupted = true; + } + } + //preserve thread interrupt state + if (interrupted) + Thread.currentThread().interrupt(); + } + + @Override + public boolean acquire(long delay) throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + + boolean success = false; + if (delay <= 0) + return attempt(); + Semaphore semaphore = createSemaphore(); + if (semaphore == null) + return true; + if (DEBUG) + System.out.println("[" + Thread.currentThread() + "] Operation waiting to be executed... " + this); //$NON-NLS-1$ //$NON-NLS-2$ + success = doAcquire(semaphore, delay); + manager.resumeSuspendedLocks(Thread.currentThread()); + if (DEBUG) + System.out.println("[" + Thread.currentThread() + //$NON-NLS-1$ + (success ? "] Operation started... " : "] Operation timed out... ") + this); //$NON-NLS-1$ //$NON-NLS-2$ //} + if (!success && Thread.interrupted()) + throw new InterruptedException(); + return success; + } + + /** + * Attempts to acquire the lock. Returns false if the lock is not available and + * true if the lock has been successfully acquired. + */ + private synchronized boolean attempt() { + //return true if we already own the lock + //also, if nobody is waiting, grant the lock immediately + if ((currentOperationThread == Thread.currentThread()) || (currentOperationThread == null && operations.isEmpty())) { + depth++; + setCurrentOperationThread(Thread.currentThread()); + return true; + } + return false; + } + + @Override + public boolean contains(ISchedulingRule rule) { + return rule == this; + } + + /** + * Returns null if acquired and a Semaphore object otherwise. If a + * waiting semaphore already exists for this thread, it will be returned, + * otherwise a new semaphore will be created, enqueued, and returned. + */ + private synchronized Semaphore createSemaphore() { + return attempt() ? null : enqueue(new Semaphore(Thread.currentThread())); + } + + /** + * Attempts to acquire this lock. Callers will block until this lock comes available to + * them, or until the specified delay has elapsed. + */ + private boolean doAcquire(Semaphore semaphore, long delay) { + boolean success = false; + //notify hook to service pending syncExecs before falling asleep + if (manager.aboutToWait(this.currentOperationThread)) { + //hook granted immediate access + //remove semaphore for the lock request from the queue + //do not log in graph because this thread did not really get the lock + removeFromQueue(semaphore); + depth++; + manager.addLockThread(currentOperationThread, this); + return true; + } + //Make sure the semaphore is in the queue before we start waiting + //It might have been removed from the queue while servicing syncExecs + //This is will return our existing semaphore if it is still in the queue + semaphore = createSemaphore(); + if (semaphore == null) + return true; + final Thread currentThread = Thread.currentThread(); + manager.addLockWaitThread(currentThread, this); + try { + success = semaphore.acquire(delay); + } catch (InterruptedException e) { + if (DEBUG) + System.out.println("[" + currentThread + "] Operation interrupted while waiting... :-|"); //$NON-NLS-1$ //$NON-NLS-2$ + //remember the interrupt to throw it later + currentThread.interrupt(); + } + return updateOperationQueue(semaphore, success); + } + + /** + * Releases this lock from the thread that used to own it. + * Grants this lock to the next thread in the queue. + */ + private synchronized void doRelease() { + //notify hook + manager.aboutToRelease(); + depth = 0; + Semaphore next = (Semaphore) operations.peek(); + setCurrentOperationThread(null); + if (next != null) + next.release(); + } + + /** + * If there is another semaphore with the same runnable in the + * queue, the other is returned and the new one is not added. + */ + private synchronized Semaphore enqueue(Semaphore newSemaphore) { + Semaphore semaphore = (Semaphore) operations.get(newSemaphore); + if (semaphore == null) { + operations.enqueue(newSemaphore); + return newSemaphore; + } + return semaphore; + } + + /** + * Suspend this lock by granting the lock to the next lock in the queue. + * Return the depth of the suspended lock. + */ + protected int forceRelease() { + int oldDepth = depth; + doRelease(); + return oldDepth; + } + + @Override + public int getDepth() { + return depth; + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + return rule == this; + } + + @Override + public void release() { + if (depth == 0) + return; + //only release the lock when the depth reaches zero + Assert.isTrue(depth >= 0, "Lock released too many times"); //$NON-NLS-1$ + if (--depth == 0) + doRelease(); + else + manager.removeLockThread(currentOperationThread, this); + } + + /** + * Removes a semaphore from the queue of waiting operations. + * + * @param semaphore The semaphore to remove + */ + private synchronized void removeFromQueue(Semaphore semaphore) { + operations.remove(semaphore); + } + + /** + * If newThread is null, release this lock from its previous owner. + * If newThread is not null, grant this lock to newThread. + */ + private void setCurrentOperationThread(Thread newThread) { + if ((currentOperationThread != null) && (newThread == null)) + manager.removeLockThread(currentOperationThread, this); + this.currentOperationThread = newThread; + if (currentOperationThread != null) + manager.addLockThread(currentOperationThread, this); + } + + /** + * Forces the lock to be at the given depth. + * Used when re-acquiring a suspended lock. + */ + protected void setDepth(int newDepth) { + for (int i = depth; i < newDepth; i++) { + manager.addLockThread(currentOperationThread, this); + } + this.depth = newDepth; + } + + /** + * For debugging purposes only. + */ + @Override + public String toString() { + return "OrderedLock (" + number + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * This lock has just been granted to a new thread (the thread waited for it). + * Remove the request from the queue and update both the graph and the lock. + */ + private synchronized void updateCurrentOperation() { + operations.dequeue(); + setCurrentOperationThread(Thread.currentThread()); + } + + /** + * We have finished waiting on the given semaphore. Update the operation queue according + * to whether we succeeded in obtaining the lock. + * + * @param semaphore The semaphore that we waited on + * @param acquired true if we successfully acquired the semaphore, and false otherwise + * @return whether the lock was successfully obtained + */ + private synchronized boolean updateOperationQueue(Semaphore semaphore, boolean acquired) { + // Bug 311863 - Semaphore may have been released concurrently, so check again before discarding it + if (!acquired) + acquired = semaphore.attempt(); + if (acquired) { + depth++; + updateCurrentOperation(); + } else { + removeFromQueue(semaphore); + manager.removeLockWaitThread(Thread.currentThread(), this); + } + return acquired; + } + +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java new file mode 100644 index 0000000000..03c1bb94cd --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * 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.*; + +/** + * A Queue of objects. + */ +public class Queue { + protected Object[] elements; + protected int head; + protected boolean reuse; + protected int tail; + + public Queue() { + this(20, false); + } + + /** + * The parameter reuse indicates what do you want to happen with + * the object reference when you remove it from the queue. If + * reuse is false the queue no longer holds a reference to the + * object when it is removed. If reuse is true you can use the + * method getNextAvailableObject to get an used object, set its + * new values and add it again to the queue. + */ + public Queue(int size, boolean reuse) { + elements = new Object[size]; + head = tail = 0; + this.reuse = reuse; + } + + /** + * Adds an object to the tail of the queue. + */ + public void enqueue(Object element) { + int newTail = increment(tail); + if (newTail == head) { + grow(); + newTail = tail + 1; + } + elements[tail] = element; + tail = newTail; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to decrement an index in the queue. + */ + public int decrement(int index) { + return (index == 0) ? (elements.length - 1) : index - 1; + } + + public Iterator elements() { + /**/ + if (isEmpty()) + return new ArrayList(0).iterator(); + + /* if head < tail we can use the same array */ + if (head <= tail) + return Arrays.asList(elements).iterator(); + + /* otherwise we need to create a new array */ + Object[] newElements = new Object[size()]; + int end = (elements.length - head); + System.arraycopy(elements, head, newElements, 0, end); + System.arraycopy(elements, 0, newElements, end, tail); + return Arrays.asList(newElements).iterator(); + } + + public Object get(Object o) { + int index = head; + while (index != tail) { + if (elements[index].equals(o)) + return elements[index]; + index = increment(index); + } + return null; + } + + /** + * Removes the given object from the queue. Shifts the underlying array. + */ + public boolean remove(Object o) { + int index = head; + //find the object to remove + while (index != tail) { + if (elements[index].equals(o)) + break; + index = increment(index); + } + //if element wasn't found, return + if (index == tail) + return false; + //store a reference to it (needed for reuse of objects) + Object toRemove = elements[index]; + int nextIndex = -1; + while (index != tail) { + nextIndex = increment(index); + if (nextIndex != tail) + elements[index] = elements[nextIndex]; + + index = nextIndex; + } + //decrement tail + tail = decrement(tail); + + //if objects are reused, transfer the reference that is removed to the end of the queue + //otherwise set the element after the last one to null (to avoid duplicate references) + elements[tail] = reuse ? toRemove : null; + return true; + } + + protected void grow() { + int newSize = (int) (elements.length * 1.5); + Object[] newElements = new Object[newSize]; + if (tail >= head) + System.arraycopy(elements, head, newElements, head, size()); + else { + int newHead = newSize - (elements.length - head); + System.arraycopy(elements, 0, newElements, 0, tail + 1); + System.arraycopy(elements, head, newElements, newHead, (newSize - newHead)); + head = newHead; + } + elements = newElements; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to increment an index in the queue. + */ + public int increment(int index) { + return (index == (elements.length - 1)) ? 0 : index + 1; + } + + public boolean isEmpty() { + return tail == head; + } + + public Object peek() { + return elements[head]; + } + + /** + * Removes an returns the item at the head of the queue. + */ + public Object dequeue() { + if (isEmpty()) + return null; + Object result = peek(); + if (!reuse) + elements[head] = null; + head = increment(head); + return result; + } + + public int size() { + return tail > head ? (tail - head) : ((elements.length - head) + tail); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("["); //$NON-NLS-1$ + if (!isEmpty()) { + Iterator it = elements(); + while (true) { + sb.append(it.next()); + if (it.hasNext()) + sb.append(", "); //$NON-NLS-1$ + else + break; + } + } + sb.append("]"); //$NON-NLS-1$ + return sb.toString(); + } +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java new file mode 100644 index 0000000000..a169b01278 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - Bug 311863 Ordered Lock lost after interrupt + *******************************************************************************/ +package org.eclipse.core.internal.jobs; + +public class Semaphore { + protected long notifications; + protected Runnable runnable; + + public Semaphore(Runnable runnable) { + this.runnable = runnable; + notifications = 0; + } + + /** + * Attempts to acquire this semaphore. Returns true if it was successfully acquired, + * and false otherwise. + */ + public synchronized boolean acquire(long delay) throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + long start = System.currentTimeMillis(); + long timeLeft = delay; + while (true) { + if (notifications > 0) { + notifications--; + return true; + } + if (timeLeft <= 0) + return false; + wait(timeLeft); + timeLeft = start + delay - System.currentTimeMillis(); + } + } + + /** + * Attempt to acquire the semaphore without waiting. + * Returns true if successfully acquired, false otherwise. + */ + public synchronized boolean attempt() { + if (notifications > 0) { + notifications--; + return true; + } + return false; + } + + @Override + public boolean equals(Object obj) { + return (runnable == ((Semaphore) obj).runnable); + } + + @Override + public int hashCode() { + return runnable == null ? 0 : runnable.hashCode(); + } + + public synchronized void release() { + notifications++; + notifyAll(); + } + + // for debug only + @Override + public String toString() { + return "Semaphore(" + runnable + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java new file mode 100644 index 0000000000..1567ac0bcd --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java @@ -0,0 +1,451 @@ +/******************************************************************************* + * 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.internal.jobs; + +import org.eclipse.core.internal.runtime.RuntimeLog; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.*; + +/** + * Captures the implicit job state for a given thread. + */ +class ThreadJob extends Job { + + /** + * Set to true if this thread job is running in a thread that did + * not own a rule already. This means it needs to acquire the + * rule during beginRule, and must release the rule during endRule. + */ + protected boolean acquireRule = false; + + /** + * Indicates that this thread job did report to the progress manager + * that it will be blocked, and therefore when it begins it must + * be reported to the job manager when it is no longer blocked. + */ + boolean isBlocked = false; + + /** + * True if this ThreadJob has begun execution + * @GuardedBy("this") + */ + protected boolean isRunning = false; + + /** + * Used for diagnosing mismatched begin/end pairs. This field + * is only used when in debug mode, to capture the stack trace + * of the last call to beginRule. + */ + private RuntimeException lastPush = null; + /** + * The actual job that is running in the thread that this + * ThreadJob represents. This will be null if this thread + * job is capturing a rule acquired outside of a job. + * @GuardedBy("JobManager.implicitJobs") + */ + protected Job realJob; + /** + * The stack of rules that have been begun in this thread, but not yet ended. + * @GuardedBy("JobManager.implicitJobs") + */ + private ISchedulingRule[] ruleStack; + /** + * Rule stack pointer. + * INV: 0 <= top <= ruleStack.length + * @GuardedBy("JobManager.implicitJobs") + */ + private int top; + + /** + * Waiting state for thread jobs is independent of the internal state. When + * this variable is true, this ThreadJob is waiting in joinRun() + * @GuardedBy("jobStateLock") + */ + boolean isWaiting; + + ThreadJob(ISchedulingRule rule) { + super("Implicit Job"); //$NON-NLS-1$ + setSystem(true); + // calling setPriority will try to acquire JobManager.lock, breaking + // lock acquisition protocol. Since we are constructing this thread, + // we can call internalSetPriority + ((InternalJob) this).internalSetPriority(Job.INTERACTIVE); + ruleStack = new ISchedulingRule[2]; + top = -1; + ((InternalJob) this).internalSetRule(rule); + } + + boolean isResumingAfterYield() { + return false; + } + + /** + * An endRule was called that did not match the last beginRule in + * the stack. Report and log a detailed informational message. + * @param rule The rule that was popped + * @GuardedBy("JobManager.implicitJobs") + */ + private void illegalPop(ISchedulingRule rule) { + StringBuffer buf = new StringBuffer("Attempted to endRule: "); //$NON-NLS-1$ + buf.append(rule); + if (top >= 0 && top < ruleStack.length) { + buf.append(", does not match most recent begin: "); //$NON-NLS-1$ + buf.append(ruleStack[top]); + } else { + if (top < 0) + buf.append(", but there was no matching beginRule"); //$NON-NLS-1$ + else + buf.append(", but the rule stack was out of bounds: " + top); //$NON-NLS-1$ + } + buf.append(". See log for trace information if rule tracing is enabled."); //$NON-NLS-1$ + String msg = buf.toString(); + if (JobManager.DEBUG || JobManager.DEBUG_BEGIN_END) { + System.out.println(msg); + Throwable t = lastPush == null ? new IllegalArgumentException() : lastPush; + IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, t); + RuntimeLog.log(error); + } + Assert.isLegal(false, msg); + } + + /** + * Client has attempted to begin a rule that is not contained within + * the outer rule. + */ + private void illegalPush(ISchedulingRule pushRule, ISchedulingRule baseRule) { + StringBuffer buf = new StringBuffer("Attempted to beginRule: "); //$NON-NLS-1$ + buf.append(pushRule); + buf.append(", does not match outer scope rule: "); //$NON-NLS-1$ + buf.append(baseRule); + String msg = buf.toString(); + if (JobManager.DEBUG) { + System.out.println(msg); + IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, new IllegalArgumentException()); + RuntimeLog.log(error); + } + Assert.isLegal(false, msg); + + } + + /** + * Returns true if the monitor is canceled, and false otherwise. + * Protects the caller from exception in the monitor implementation. + */ + static private boolean isCanceled(IProgressMonitor monitor) { + try { + return monitor.isCanceled(); + } catch (RuntimeException e) { + //logged message should not be translated + IStatus status = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "ThreadJob.isCanceled", e); //$NON-NLS-1$ + RuntimeLog.log(status); + } + return false; + } + + /** + * Returns true if this thread job was scheduled and actually started running. + * @GuardedBy("this") + */ + synchronized boolean isRunning() { + return isRunning; + } + + /** + * A reentrant method which will run this ThreadJob immediately if there + * are no existing jobs with conflicting rules, or block until the rule can be acquired. If this + * job must block, the LockListener is given a chance to override. + * If override is not granted, then this method will block until the rule is available. If + * LockListener#canBlock returns true, then the monitor + * will not be periodically checked for cancellation. It will only be rechecked if this + * thread is interrupted. If LockListener#canBlock returns false The + * monitor will be checked periodically for cancellation. + * + * When a UI is present, it is recommended that the LockListener + * should not allow the UI thread to block without checking the monitor. This + * ensures that the UI remains responsive. + * + * @see LockListener#aboutToWait(Thread) + * @see LockListener#canBlock() + * @see JobManager#transferRule(ISchedulingRule, Thread) + + * @return this, or the ThreadJob instance that was + * unblocked (due to transferRule) in the case of reentrant invocations of this method. + * + * @param monitor - The IProgressMonitor used to report blocking status and + * cancellation. + * + * @throws OperationCanceledException if this job was canceled before it was started. + */ + static ThreadJob joinRun(ThreadJob threadJob, IProgressMonitor monitor) { + if (isCanceled(monitor)) + throw new OperationCanceledException(); + // check if there is a blocking thread before waiting + InternalJob blockingJob = manager.findBlockingJob(threadJob); + Thread blocker = blockingJob == null ? null : blockingJob.getThread(); + try { + // just return if lock listener decided to grant immediate access + if (manager.getLockManager().aboutToWait(blocker)) + return threadJob; + return waitForRun(threadJob, monitor, blockingJob, blocker); + } finally { + manager.getLockManager().aboutToRelease(); + } + } + + static ThreadJob waitForRun(ThreadJob threadJob, IProgressMonitor monitor, InternalJob blockingJob, Thread blocker) { + // Ask lock manager if it safe to block this thread + final boolean canBlock = manager.getLockManager().canBlock(); + ThreadJob result = threadJob; + boolean interrupted = false; + boolean waiting = false; + try { + waitStart(threadJob, monitor, blockingJob); + manager.implicitJobs.addWaiting(threadJob); + waiting = true; + // If we're allowed to block this thread we won't be checking the monitor. In order + // to respond to cancellation, register this monitor with the internal JobManager + // worker thread. The worker thread will check for cancellation and will interrupt + // this thread when the monitor is canceled. T + if (canBlock) + manager.beginMonitoring(threadJob, monitor); + final Thread currentThread = Thread.currentThread(); + + // Ultimately, this loop will wait until the job "runs" (acquires the rule) + // or is canceled. However, there are many ways for that to occur. + // The exit conditions of this loop are: + // 1) This job no longer conflicts with any other running job. + // 2) The LockManager#aboutToWait allowed this thread to run. + // This usually occurs during reentrant situations. i.e. This is a UI thread, + // and a syncExec is performed from conflicting job/thread. + // 3) A rule is transfered to this thread. This can be invoked programmatically, + // or commonly in JFace via ModalContext (for wizards/etc). + // 4) Monitor is canceled. + while (true) { + // monitor is foreign code so do not hold locks while calling into monitor + if (isCanceled(monitor)) + // Condition #4. + throw new OperationCanceledException(); + // Try to run the job. If result is null, this job was allowed to run. + // If the result is successful, atomically release thread from waiting + // status. + blockingJob = manager.runNow(threadJob, true); + if (blockingJob == null) { + // Condition #1. + waiting = false; + return threadJob; + } + blocker = blockingJob == null ? null : blockingJob.getThread(); + // the rule could have been transferred to this thread while we were waiting + if (blocker == currentThread && blockingJob instanceof ThreadJob) { + // now we are just the nested acquire case + result = (ThreadJob) blockingJob; + result.push(threadJob.getRule()); + result.isBlocked = threadJob.isBlocked; + // Condition #3. + return result; + } + // just return if lock listener decided to grant immediate access + if (manager.getLockManager().aboutToWait(blocker)) + // Condition #2. + return threadJob; + + // Notify the lock manager that we're about to block waiting for the scheduling rule + manager.getLockManager().addLockWaitThread(currentThread, threadJob.getRule()); + synchronized (blockingJob.jobStateLock) { + try { + // Wait until we are no longer definitely blocked (not running). + // The actual exit conditions are listed above at the beginning of + // this while loop + int state = blockingJob.getState(); + //ensure we don't wait forever if the blocker is waiting, because it might have yielded to me + if (state == Job.RUNNING && canBlock) + blockingJob.jobStateLock.wait(); + else if (state != Job.NONE) + blockingJob.jobStateLock.wait(250); + } catch (InterruptedException e) { + // This thread may be interrupted via two common scenarios. 1) If + // the UISynchronizer is in use and this thread is a UI thread + // and a syncExec() is performed, this thread will be interrupted + // every 1000ms. 2) If this thread is allowed to be blocked and + // the progress monitor was canceled, the internal JobManager + // worker thread will interrupt this thread so cancellation can + // be carried out. + interrupted = true; + } + } + // Going around the loop again. Ensure we're not marked as waiting for the thread + // as external code is run via the monitor (Bug 262032). + manager.getLockManager().removeLockWaitThread(currentThread, threadJob.getRule()); + } + } finally { + if (interrupted) + Thread.currentThread().interrupt(); + //only update the lock state if we ended up using the thread job that was given to us + waitEnd(threadJob, threadJob == result, monitor); + if (threadJob == result) { + if (waiting) + manager.implicitJobs.removeWaiting(threadJob); + } + if (canBlock) + // must unregister monitoring this job + manager.endMonitoring(threadJob); + } + } + + /** + * Pops a rule. Returns true if it was the last rule for this thread + * job, and false otherwise. + * @GuardedBy("JobManager.implicitJobs") + */ + boolean pop(ISchedulingRule rule) { + if (top < 0 || ruleStack[top] != rule) + illegalPop(rule); + ruleStack[top--] = null; + return top < 0; + } + + /** + * Adds a new scheduling rule to the stack of rules for this thread. Throws + * a runtime exception if the new rule is not compatible with the base + * scheduling rule for this thread. + * @GuardedBy("JobManager.implicitJobs") + */ + void push(final ISchedulingRule rule) { + final ISchedulingRule baseRule = getRule(); + if (++top >= ruleStack.length) { + ISchedulingRule[] newStack = new ISchedulingRule[ruleStack.length * 2]; + System.arraycopy(ruleStack, 0, newStack, 0, ruleStack.length); + ruleStack = newStack; + } + ruleStack[top] = rule; + if (JobManager.DEBUG_BEGIN_END) + lastPush = (RuntimeException) new RuntimeException().fillInStackTrace(); + //check for containment last because we don't want to fail again on endRule + if (baseRule != null && rule != null && !(baseRule.contains(rule) && baseRule.isConflicting(rule))) + illegalPush(rule, baseRule); + } + + /** + * Reset all of this job's fields so it can be reused. Returns false if + * reuse is not possible + * @GuardedBy("JobManager.implicitJobs") + */ + boolean recycle() { + //don't recycle if still running for any reason + if (getState() != Job.NONE) + return false; + //clear and reset all fields + acquireRule = isRunning = isBlocked = false; + realJob = null; + setRule(null); + setThread(null); + if (ruleStack.length != 2) + ruleStack = new ISchedulingRule[2]; + else + ruleStack[0] = ruleStack[1] = null; + top = -1; + return true; + } + + /** (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public IStatus run(IProgressMonitor monitor) { + synchronized (this) { + isRunning = true; + } + return ASYNC_FINISH; + } + + /** + * Records the job that is actually running in this thread, if any + * @param realJob The running job + * @GuardedBy("JobManager.implicitJobs") + */ + void setRealJob(Job realJob) { + this.realJob = realJob; + } + + /** + * Returns true if this job should cause a self-canceling job + * to cancel itself, and false otherwise. + * @GuardedBy("JobManager.implicitJobs") + */ + boolean shouldInterrupt() { + return realJob == null ? true : !realJob.isSystem(); + } + + /* (non-javadoc) + * For debugging purposes only + */ + @Override + public String toString() { + StringBuffer buf = new StringBuffer("ThreadJob"); //$NON-NLS-1$ + buf.append('(').append(realJob).append(',').append(getRuleStack()).append(')'); + return buf.toString(); + } + + String getRuleStack() { + StringBuffer buf = new StringBuffer(); + buf.append('['); + for (int i = 0; i <= top && i < ruleStack.length; i++) { + buf.append(ruleStack[i]); + if (i != top) + buf.append(','); + } + buf.append(']'); + return buf.toString(); + } + + /** + * Reports that this thread was blocked, but is no longer blocked and is able + * to proceed. + * @param monitor The monitor to report unblocking to. + */ + static private void waitEnd(ThreadJob threadJob, boolean updateLockManager, IProgressMonitor monitor) { + if (updateLockManager) { + final LockManager lockManager = manager.getLockManager(); + final Thread currentThread = Thread.currentThread(); + if (threadJob.isRunning()) { + lockManager.addLockThread(currentThread, threadJob.getRule()); + //need to re-acquire any locks that were suspended while this thread was blocked on the rule + lockManager.resumeSuspendedLocks(currentThread); + } else { + //tell lock manager that this thread gave up waiting + lockManager.removeLockWaitThread(currentThread, threadJob.getRule()); + } + } + if (threadJob.isBlocked) { + threadJob.isBlocked = false; + manager.reportUnblocked(monitor); + } + } + + /** + * Indicates the start of a wait on a scheduling rule. Report the + * blockage to the progress manager. + * @param monitor The monitor to report blocking to + * @param blockingJob The job that is blocking this thread, or null + */ + static private void waitStart(ThreadJob threadJob, IProgressMonitor monitor, InternalJob blockingJob) { + threadJob.isBlocked = true; + manager.reportBlocked(monitor, blockingJob); + } + + /** + * ThreadJobs are one-shot jobs, and they must ignore all attempts to schedule them. + */ + @Override + public boolean shouldSchedule() { + return false; + } +} \ No newline at end of file diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java new file mode 100644 index 0000000000..0fa63c3a3e --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.internal.jobs; + +import org.eclipse.core.internal.runtime.RuntimeLog; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; + +/** + * A worker thread processes jobs supplied to it by the worker pool. When + * the worker pool gives it a null job, the worker dies. + */ +public class Worker extends Thread { + //worker number used for debugging purposes only + private static int nextWorkerNumber = 0; + private volatile InternalJob currentJob; + private final WorkerPool pool; + + public Worker(WorkerPool pool) { + super("Worker-" + nextWorkerNumber++); //$NON-NLS-1$ + this.pool = pool; + //set the context loader to avoid leaking the current context loader + //for the thread that spawns this worker (bug 98376) + setContextClassLoader(pool.defaultContextLoader); + } + + /** + * Returns the currently running job, or null if none. + */ + public Job currentJob() { + return (Job) currentJob; + } + + private IStatus handleException(InternalJob job, Throwable t) { + String message = NLS.bind(JobMessages.jobs_internalError, job.getName()); + return new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, message, t); + } + + @Override + public void run() { + setPriority(Thread.NORM_PRIORITY); + try { + while ((currentJob = pool.startJob(this)) != null) { + IStatus result = Status.OK_STATUS; + try { + result = currentJob.run(currentJob.getProgressMonitor()); + } catch (OperationCanceledException e) { + result = Status.CANCEL_STATUS; + } catch (Exception e) { + result = handleException(currentJob, e); + } catch (ThreadDeath e) { + //must not consume thread death + result = handleException(currentJob, e); + throw e; + } catch (Error e) { + result = handleException(currentJob, e); + } finally { + //clear interrupted state for this thread + Thread.interrupted(); + //result must not be null + if (result == null) + result = handleException(currentJob, new NullPointerException()); + pool.endJob(currentJob, result); + currentJob = null; + //reset thread priority in case job changed it + setPriority(Thread.NORM_PRIORITY); + } + } + } catch (Throwable t) { + RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "Unhandled error", t)); //$NON-NLS-1$ + } finally { + currentJob = null; + pool.endWorker(this); + } + } +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java new file mode 100644 index 0000000000..ee38ffdef7 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * 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.Assert; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.jobs.Job; + +/** + * Maintains a pool of worker threads. Threads are constructed lazily as + * required, and are eventually discarded if not in use for awhile. This class + * maintains the thread creation/destruction policies for the job manager. + * + * Implementation note: all the data structures of this class are protected + * by the instance's object monitor. To avoid deadlock with third party code, + * this lock is never held when calling methods outside this class that may in + * turn use locks. + */ +class WorkerPool { + /** + * Threads not used by their best before timestamp are destroyed. + */ + private static final int BEST_BEFORE = 60000; + /** + * There will always be at least MIN_THREADS workers in the pool. + */ + private static final int MIN_THREADS = 1; + /** + * Use the busy thread count to avoid starting new threads when a living + * thread is just doing house cleaning (notifying listeners, etc). + */ + private int busyThreads = 0; + + /** + * The default context class loader to use when creating worker threads. + */ + protected final ClassLoader defaultContextLoader; + + /** + * Records whether new worker threads should be daemon threads. + */ + private boolean isDaemon = false; + + private JobManager manager; + /** + * The number of workers in the threads array + */ + private int numThreads = 0; + /** + * The number of threads that are currently sleeping + */ + private int sleepingThreads = 0; + /** + * The living set of workers in this pool. + */ + private Worker[] threads = new Worker[10]; + + protected WorkerPool(JobManager manager) { + this.manager = manager; + this.defaultContextLoader = Thread.currentThread().getContextClassLoader(); + } + + /** + * Adds a worker to the list of workers. + */ + private synchronized void add(Worker worker) { + int size = threads.length; + if (numThreads + 1 > size) { + Worker[] newThreads = new Worker[2 * size]; + System.arraycopy(threads, 0, newThreads, 0, size); + threads = newThreads; + } + threads[numThreads++] = worker; + } + + private synchronized void decrementBusyThreads() { + //impossible to have less than zero busy threads + if (--busyThreads < 0) { + if (JobManager.DEBUG) + Assert.isTrue(false, Integer.toString(busyThreads)); + busyThreads = 0; + } + } + + /** + * Signals the end of a job. Note that this method can be called under + * OutOfMemoryError conditions and thus must be paranoid about allocating objects. + */ + protected void endJob(InternalJob job, IStatus result) { + try { + //need to end rule in graph before ending job so that 2 threads + //do not become the owners of the same rule in the graph + if ((job.getRule() != null) && !(job instanceof ThreadJob)) { + //remove any locks this thread may be owning on that rule + manager.getLockManager().removeLockCompletely(Thread.currentThread(), job.getRule()); + } + manager.endJob(job, result, true); + //ensure this thread no longer owns any scheduling rules + manager.implicitJobs.endJob(job); + } finally { + decrementBusyThreads(); + } + } + + /** + * Signals the death of a worker thread. Note that this method can be called under + * OutOfMemoryError conditions and thus must be paranoid about allocating objects. + */ + protected synchronized void endWorker(Worker worker) { + if (remove(worker) && JobManager.DEBUG) + JobManager.debug("worker removed from pool: " + worker); //$NON-NLS-1$ + } + + private synchronized void incrementBusyThreads() { + //impossible to have more busy threads than there are threads + if (++busyThreads > numThreads) { + if (JobManager.DEBUG) + Assert.isTrue(false, Integer.toString(busyThreads) + ',' + numThreads); + busyThreads = numThreads; + } + } + + /** + * Notification that a job has been added to the queue. Wake a worker, + * creating a new worker if necessary. The provided job may be null. + */ + protected synchronized void jobQueued() { + //if there is a sleeping thread, wake it up + if (sleepingThreads > 0) { + notify(); + return; + } + //create a thread if all threads are busy + if (busyThreads >= numThreads) { + Worker worker = new Worker(this); + worker.setDaemon(isDaemon); + add(worker); + if (JobManager.DEBUG) + JobManager.debug("worker added to pool: " + worker); //$NON-NLS-1$ + worker.start(); + return; + } + } + + /** + * Remove a worker thread from our list. + * @return true if a worker was removed, and false otherwise. + */ + private synchronized boolean remove(Worker worker) { + for (int i = 0; i < threads.length; i++) { + if (threads[i] == worker) { + System.arraycopy(threads, i + 1, threads, i, numThreads - i - 1); + threads[--numThreads] = null; + return true; + } + } + return false; + } + + /** + * Sets whether threads created in the worker pool should be daemon threads. + */ + void setDaemon(boolean value) { + this.isDaemon = value; + } + + protected synchronized void shutdown() { + notifyAll(); + } + + /** + * Sleep for the given duration or until woken. + */ + private synchronized void sleep(long duration) { + sleepingThreads++; + busyThreads--; + if (JobManager.DEBUG) + JobManager.debug("worker sleeping for: " + duration + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + try { + wait(duration); + } catch (InterruptedException e) { + if (JobManager.DEBUG) + JobManager.debug("worker interrupted while waiting... :-|"); //$NON-NLS-1$ + } finally { + sleepingThreads--; + busyThreads++; + } + } + + /** + * Returns a new job to run. Returns null if the thread should die. + */ + protected InternalJob startJob(Worker worker) { + //if we're above capacity, kill the thread + synchronized (this) { + if (!manager.isActive()) { + //must remove the worker immediately to prevent all threads from expiring + endWorker(worker); + return null; + } + //set the thread to be busy now in case of reentrant scheduling + incrementBusyThreads(); + } + Job job = null; + try { + job = manager.startJob(worker); + //spin until a job is found or until we have been idle for too long + long idleStart = System.currentTimeMillis(); + while (manager.isActive() && job == null) { + long hint = manager.sleepHint(); + if (hint > 0) + sleep(Math.min(hint, BEST_BEFORE)); + job = manager.startJob(worker); + //if we were already idle, and there are still no new jobs, then + // the thread can expire + synchronized (this) { + if (job == null && (System.currentTimeMillis() - idleStart > BEST_BEFORE) && (numThreads - busyThreads) > MIN_THREADS) { + //must remove the worker immediately to prevent all threads from expiring + endWorker(worker); + return null; + } + } + //if we didn't sleep but there was no job available, make sure we sleep to avoid a tight loop (bug 260724) + if (hint <= 0 && job == null) + sleep(50); + } + if (job != null) { + //if this job has a rule, then we are essentially acquiring a lock + if ((job.getRule() != null) && !(job instanceof ThreadJob)) { + //don't need to re-acquire locks because it was not recorded in the graph + //that this thread waited to get this rule + manager.getLockManager().addLockThread(Thread.currentThread(), job.getRule()); + } + //see if we need to wake another worker + if (manager.sleepHint() < InternalJob.T_INFINITE) + jobQueued(); + } + } finally { + //decrement busy thread count if we're not running a job + if (job == null) + decrementBusyThreads(); + } + return job; + } +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.properties b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.properties new file mode 100644 index 0000000000..8451b403e6 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.properties @@ -0,0 +1,21 @@ +############################################################################### +# 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 +############################################################################### +### Runtime jobs plugin messages + +### Job Manager and Locks +jobs_blocked0=The user operation is waiting for background work to complete. +jobs_blocked1=The user operation is waiting for \"{0}\" to complete. +jobs_internalError=An internal error occurred during: \"{0}\". +jobs_waitFamSub={0} operations remaining. +jobs_waitFamSubOne={0} operation remaining. + +### metadata +meta_pluginProblems = Problems occurred when invoking code from plug-in: \"{0}\". diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java new file mode 100644 index 0000000000..5cbf0839a6 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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.runtime.jobs; + +import org.eclipse.core.runtime.IStatus; + +/** + * An event describing a change to the state of a job. + * + * @see IJobChangeListener + * @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 IJobChangeEvent { + /** + * The amount of time in milliseconds to wait after scheduling the job before it + * should be run, or -1 if not applicable for this type of event. + * This value is only applicable for the scheduled event. + * + * @return the delay time for this event + */ + public long getDelay(); + + /** + * The job on which this event occurred. + * + * @return the job for this event + */ + public Job getJob(); + + /** + * The result returned by the job's run method, or null if + * not applicable. This value is only applicable for the done event. + * + * @return the status for this event + */ + public IStatus getResult(); + + /** + * 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. This value is only applicable for the done event. + * + * @return the job group status for this event, or null + * @since 3.7 + */ + public IStatus getJobGroupResult(); +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java new file mode 100644 index 0000000000..26ee6e1673 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * 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; + +/** + * Callback interface for clients interested in being notified when jobs change state. + *

                          + * A single job listener instance can be added either to the job manager, for notification + * of all scheduled jobs, or to any set of individual jobs. A single listener instance should + * not be added to both the job manager, and to individual jobs (such a listener may + * receive duplicate notifications). + *

                          + * Clients should not rely on the result of the Job#getState() + * method on jobs for which notification is occurring. Listeners are notified of + * all job state changes, but whether the state change occurs before, during, or + * after listeners are notified is unspecified. + *

                          + * Clients may implement this interface. + *

                          + * @see JobChangeAdapter + * @see IJobManager#addJobChangeListener(IJobChangeListener) + * @see IJobManager#removeJobChangeListener(IJobChangeListener) + * @see Job#addJobChangeListener(IJobChangeListener) + * @see Job#getState() + * @see Job#removeJobChangeListener(IJobChangeListener) + * @since 3.0 + */ +public interface IJobChangeListener { + /** + * Notification that a job is about to be run. Listeners are allowed to sleep, cancel, + * or change the priority of the job before it is started (and as a result may prevent + * the run from actually occurring). + * + * @param event the event details + */ + public void aboutToRun(IJobChangeEvent event); + + /** + * Notification that a job was previously sleeping and has now been rescheduled + * to run. + * + * @param event the event details + */ + public void awake(IJobChangeEvent event); + + /** + * Notification that a job has completed execution, either due to cancelation, successful + * completion, or failure. The event status object indicates how the job finished, + * and the reason for failure, if applicable. + * + * @param event the event details + */ + public void done(IJobChangeEvent event); + + /** + * Notification that a job has started running. + * + * @param event the event details + */ + public void running(IJobChangeEvent event); + + /** + * Notification that a job is being added to the queue of scheduled jobs. + * The event details includes the scheduling delay before the job should start + * running. + * + * @param event the event details, including the job instance and the scheduling + * delay + */ + public void scheduled(IJobChangeEvent event); + + /** + * Notification that a job was waiting to run and has now been put in the + * sleeping state. + * + * @param event the event details + */ + public void sleeping(IJobChangeEvent event); +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java new file mode 100644 index 0000000000..0cadd6b67c --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2014, 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 + * Lars Vogel - Bug 474276 + *******************************************************************************/ +package org.eclipse.core.runtime.jobs; + +import org.eclipse.core.runtime.*; + +/** + * This is a functional interface representation of {@link Job}, suitable + * for use in lambda expressions. + * + * @see Job#create(String, IJobFunction) + * @since 3.6 + */ +public interface IJobFunction { + /** + * 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 Job#ASYNC_FINISH}. Jobs + * that finish asynchronously must specify the execution thread by + * calling {@link Job#setThread(Thread)}, and must indicate when they are + * finished by calling the method {@link Job#done(IStatus)}. + * + * @param monitor + * the monitor to be used for reporting progress and responding + * to cancellation. The monitor is never {@code null}. It is the + * caller's responsibility to call + * {@link IProgressMonitor#done()} after this method returns or + * throws an exception. + * + * @return resulting status of the run. The result must not be {@code null}. + * @see Job#ASYNC_FINISH + * @see Job#done(IStatus) + */ + public IStatus run(IProgressMonitor monitor); +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java new file mode 100644 index 0000000000..454bcef845 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java @@ -0,0 +1,426 @@ +/******************************************************************************* + * 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.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +/** + * The job manager provides facilities for scheduling, querying, and maintaining jobs + * and locks. In particular, the job manager provides the following services: + *

                            + *
                          • Maintains a queue of jobs that are waiting to be run. Items can be added to + * the queue using the schedule method.
                          • + *
                          • Allows manipulation of groups of jobs called job families. Job families can + * be canceled, put to sleep, or woken up atomically. There is also a mechanism + * for querying the set of known jobs in a given family.
                          • + *
                          • Allows listeners to find out about progress on running jobs, and to find out + * when jobs have changed states.
                          • + *
                          • Provides a factory for creating lock objects. Lock objects are smart monitors + * that have strategies for avoiding deadlock.
                          • + *
                          • Provide feedback to a client that is waiting for a given job or family of jobs + * to complete.
                          • + *
                          + * + * @see Job + * @see ILock + * @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 IJobManager { + /** + * A system property key indicating whether the job manager should create + * job threads as daemon threads. Set to true to force all worker + * threads to be created as daemon threads. Set to false to force + * all worker threads to be created as non-daemon threads. + * @since 3.3 + */ + public static final String PROP_USE_DAEMON_THREADS = "eclipse.jobs.daemon"; //$NON-NLS-1$ + + /** + * Registers a job listener with the job manager. + * Has no effect if an identical listener is already registered. + * + * @param listener the listener to be added + * @see #removeJobChangeListener(IJobChangeListener) + * @see IJobChangeListener + */ + public void addJobChangeListener(IJobChangeListener listener); + + /** + * Begins applying this rule in the calling thread. If the rule conflicts with another + * rule currently running in another thread, this method blocks until there are + * no conflicting rules. Calls to beginRule must eventually be followed + * by a matching call to endRule in the same thread and with the identical + * rule instance. + *

                          + * Rules can be nested only if the rule for the inner beginRule + * is contained within the rule for the outer beginRule. Rule containment + * is tested with the API method ISchedulingRule.contains. Also, begin/end + * pairs must be strictly nested. Only the rule that has most recently begun + * can be ended at any given time. + *

                          + * A rule of null can be used, but will be ignored for scheduling + * purposes. The outermost non-null rule in the thread will be used for scheduling. A + * null rule that is begun must still be ended. + *

                          + * If this method is called from within a job that has a scheduling rule, the + * given rule must also be contained within the rule for the running job. + *

                          + * Note that endRule must be called even if beginRule fails. + * The recommended usage is: + *

                          +	 * final ISchedulingRule rule = ...;
                          +	 * try {
                          +	 * 	manager.beginRule(rule, monitor);
                          +	 * } finally {
                          +	 * 	manager.endRule(rule);
                          +	 * }
                          +	 * 
                          + * + * @param rule the rule to begin applying in this thread, or null + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @throws IllegalArgumentException if the rule is not strictly nested within + * all other rules currently active for this thread + * @throws OperationCanceledException if the supplied monitor reports cancelation + * before the rule becomes available + * @see ISchedulingRule#contains(ISchedulingRule) + */ + public void beginRule(ISchedulingRule rule, IProgressMonitor monitor); + + /** + * Cancels all jobs in the given job family. Jobs in the family that are currently waiting + * 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. + * + * @param family the job family to cancel, or null to cancel all jobs + * @see Job#belongsTo(Object) + */ + public void cancel(Object family); + + /** + * Returns a progress monitor that can be used to provide + * aggregated progress feedback on a set of running jobs. A user + * interface will typically group all jobs in a progress group together, + * providing progress feedback for individual jobs as well as aggregated + * progress for the entire group. Jobs in the group may be run sequentially, + * in parallel, or some combination of the two. + *

                          + * Recommended usage (this snippet runs two jobs in sequence in a + * single progress group): + *

                          +	 *    Job parseJob, compileJob;
                          +	 *    IProgressMonitor pm = Platform.getJobManager().createProgressGroup();
                          +	 *    try {
                          +	 *       pm.beginTask("Building", 10);
                          +	 *       parseJob.setProgressGroup(pm, 5);
                          +	 *       parseJob.schedule();
                          +	 *       compileJob.setProgressGroup(pm, 5);
                          +	 *       compileJob.schedule();
                          +	 *       parseJob.join();
                          +	 *       compileJob.join();
                          +	 *    } finally {
                          +	 *       pm.done();
                          +	 *    }
                          +	 * 
                          + * + * @see Job#setProgressGroup(IProgressMonitor, int) + * @see IProgressMonitor + * @return a progress monitor + */ + public IProgressMonitor createProgressGroup(); + + /** + * Returns the scheduling rule currently held by this thread, or null + * if the current thread does not hold any scheduling rule. + *

                          + * If this method is called from within the scope of a running job with a non-null + * scheduling rule, then this method is equivalent to calling currentJob().getRule(). + * Otherwise, this method will return the first scheduling rule obtained by this + * thread via {@link #beginRule(ISchedulingRule, IProgressMonitor)} that has not + * yet had a corresponding call to {@link #endRule(ISchedulingRule)}. + *

                          + * + * @return the current rule or null + * @since 3.5 + */ + public ISchedulingRule currentRule(); + + /** + * Returns the job that is currently running in this thread, or null if there + * is no currently running job. + * + * @return the job or null + */ + public Job currentJob(); + + /** + * Ends the application of a rule to the calling thread. Calls to endRule + * must be preceded by a matching call to beginRule in the same thread + * with an identical rule instance. + *

                          + * Rules can be nested only if the rule for the inner beginRule + * is contained within the rule for the outer beginRule. Also, begin/end + * pairs must be strictly nested. Only the rule that has most recently begun + * can be ended at any given time. + * + * @param rule the rule to end applying in this thread + * @throws IllegalArgumentException if this method is called on a rule for which + * there is no matching begin, or that does not match the most recent begin. + * @see ISchedulingRule#contains(ISchedulingRule) + */ + public void endRule(ISchedulingRule rule); + + /** + * Returns all waiting, executing and sleeping jobs belonging + * to the given family. If no jobs are found, an empty array is returned. + * + * @param family the job family to find, or null to find all jobs + * @return the job array + * @see Job#belongsTo(Object) + */ + public Job[] find(Object family); + + /** + * Returns whether the job manager is currently idle. The job manager is + * idle if no jobs are currently running or waiting to run. + * + * @return true if the job manager is idle, and + * false otherwise + * @since 3.1 + */ + public boolean isIdle(); + + /** + * Returns whether the job manager is currently suspended. + * + * @return true if the job manager is suspended, and + * false otherwise + * @since 3.4 + * @see #suspend() + * @see #resume() + */ + public boolean isSuspended(); + + /** + * Waits until all jobs of the given family are finished. This method will block the + * calling thread until all such jobs have finished executing, or until this thread is + * interrupted. If there are no jobs in the family that are currently waiting, running, + * or sleeping, this method returns immediately. Feedback on how the join is + * progressing is provided to a 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 + * in the family in the {@link Job#RUNNING} state, this method returns. + *

                          + *

                          + * 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. This method can also result in starvation of the current thread if + * another thread continues to add jobs of the given family, or if a + * job in the given family reschedules itself in an infinite loop. + *

                          + * + * @param family the job family to join, or null to join all jobs. + * @param monitor Progress monitor for reporting progress on how the + * wait is progressing, or null if no progress monitoring is required. + * @exception InterruptedException if this thread is interrupted while waiting + * @exception OperationCanceledException if the progress monitor is canceled while waiting + * @see Job#belongsTo(Object) + * @see #suspend() + */ + public void join(Object family, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException; + + /** + * Creates a new lock object. All lock objects supplied by the job manager + * know about each other and will always avoid circular deadlock amongst + * themselves. + * + * @return the new lock object + */ + public ILock newLock(); + + /** + * Removes a job listener from the job manager. + * Has no effect if an identical listener is not already registered. + * + * @param listener the listener to be removed + * @see #addJobChangeListener(IJobChangeListener) + * @see IJobChangeListener + */ + public void removeJobChangeListener(IJobChangeListener listener); + + /** + * Resumes execution of jobs after a previous suspend. All + * jobs that were sleeping or waiting prior to the suspension, or that were + * scheduled while the job manager was suspended, will now be eligible + * for execution. + *

                          + * Calling this method on a rule that is not suspended has no effect. If another + * thread also owns the rule at the time this method is called, then the rule will + * not be resumed until all threads have released the rule. + * + * @deprecated This method is not safe and should not be used. + * Suspending a scheduling rule violates the thread safety + * of clients that use scheduling rules as a mutual exclusion mechanism, + * and can result in concurrency problems in all clients that use the suspended rule. + * @see #suspend(ISchedulingRule, IProgressMonitor) + */ + @Deprecated + public void resume(ISchedulingRule rule); + + /** + * Resumes execution of jobs after a previous suspend. All + * jobs that were sleeping or waiting prior to the suspension, or that were + * scheduled while the job manager was suspended, will now be eligible + * for execution. + *

                          + * Calling resume when the job manager is not suspended + * has no effect. + * + * @see #suspend() + * @see #isSuspended() + */ + public void resume(); + + /** + * Provides a hook that is notified whenever a thread is about to wait on a lock, + * or when a thread is about to release a lock. This hook must only be set once. + *

                          + * This method is for internal use by the platform-related plug-ins. + * Clients should not call this method. + *

                          + * @see LockListener + */ + public void setLockListener(LockListener listener); + + /** + * Registers a progress provider with the job manager. If there was a + * provider already registered, it is replaced. + *

                          + * This method is intended for use by the currently executing Eclipse application. + * Plug-ins outside the currently running application should not call this method. + *

                          + * + * @param provider the new provider, or null if no progress + * is needed + */ + public void setProgressProvider(ProgressProvider provider); + + /** + * Suspends execution of all jobs. Jobs that are already running + * when this method is invoked will complete as usual, but all sleeping and + * waiting jobs will not be executed until the job manager is resumed. + *

                          + * The job manager will remain suspended until a subsequent call to + * resume. Further calls to suspend + * when the job manager is already suspended are ignored. + *

                          + * All attempts to join sleeping and waiting jobs while the job manager is + * suspended will return immediately. + *

                          + * Note that this very powerful function should be used with extreme caution. + * Suspending the job manager will prevent all jobs in the system from executing, + * which may have adverse affects on components that are relying on + * execution of jobs. The job manager should never be suspended without intent + * to resume execution soon afterwards. + * + * @see #resume() + * @see #join(Object, IProgressMonitor) + * @see #isSuspended() + */ + public void suspend(); + + /** + * Defers execution of all jobs with scheduling rules that conflict with the + * given rule. The caller will be blocked until all currently executing jobs with + * conflicting rules are completed. Conflicting jobs that are sleeping or waiting at + * the time this method is called will not be executed until the rule is resumed. + *

                          + * While a rule is suspended, all calls to beginRule and + * endRule on a suspended rule will not block the caller. + * The rule remains suspended until a subsequent call to + * resume(ISchedulingRule) with the identical rule instance. + * Further calls to suspend with an identical rule prior to calling + * resume are ignored. + *

                          + *

                          + * This method is long-running; progress and cancelation are provided by + * the given progress monitor. In the case of cancelation, the rule will + * not be suspended. + *

                          + * Note: this very powerful function should be used with extreme caution. + * Suspending rules will prevent jobs in the system from executing, which may + * have adverse effects on components that are relying on execution of jobs. + * The job manager should never be suspended without intent to resume + * execution soon afterwards. Deadlock will result if the thread responsible + * for resuming the rule attempts to join a suspended job. + * + * @deprecated This method is not safe and should not be used. + * Suspending a scheduling rule violates the thread safety + * of clients that use scheduling rules as a mutual exclusion mechanism, + * and can result in concurrency problems in all clients that use the suspended rule. + * @param rule The scheduling rule to suspend. Must not be null. + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #resume(ISchedulingRule) + */ + @Deprecated + public void suspend(ISchedulingRule rule, IProgressMonitor monitor); + + /** + * Requests that all jobs in the given job family be suspended. Jobs currently + * waiting to be run will be removed from the queue and moved into the + * SLEEPING state. Jobs that have been put to sleep + * will remain in that state until either resumed or canceled. This method has + * no effect on jobs that are not currently waiting to be run. + *

                          + * Sleeping jobs can be resumed using wakeUp. + * + * @param family the job family to sleep, or null to sleep all jobs. + * @see Job#belongsTo(Object) + */ + public void sleep(Object family); + + /** + * Transfers ownership of a scheduling rule to another thread. The identical + * scheduling rule must currently be owned by the calling thread as a result of + * a previous call to beginRule. The destination thread must + * not already own a scheduling rule. + *

                          + * Calling this method is equivalent to atomically calling endRule + * in the calling thread followed by an immediate beginRule in + * the destination thread. The destination thread is responsible for subsequently + * calling endRule when it is finished using the rule. + *

                          + * This method has no effect when the destination thread is the same as the + * calling thread. + * + * @param rule The scheduling rule to transfer + * @param destinationThread The new owner for the transferred rule. + * @since 3.1 + */ + public void transferRule(ISchedulingRule rule, Thread destinationThread); + + /** + * Resumes scheduling of all sleeping jobs in the given family. This method + * has no effect on jobs in the family that are not currently sleeping. + * + * @param family the job family to wake up, or null to wake up all jobs + * @see Job#belongsTo(Object) + */ + public void wakeUp(Object family); +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java new file mode 100644 index 0000000000..07cd46299f --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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.runtime.IStatus; + +/** + * Represents status relating to the execution of jobs. + * + * @see org.eclipse.core.runtime.IStatus + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IJobStatus extends IStatus { + /** + * Returns the job associated with this status. + * + * @return the job associated with this status + */ + public Job getJob(); +} diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java new file mode 100644 index 0000000000..d19276e6fd --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * 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; + +/** + * A lock is used to control access to an exclusive resource. + *

                          + * Locks are reentrant. That is, they can be acquired multiple times by the same thread + * without releasing. Locks are only released when the number of successful acquires + * equals the number of successful releases. + *

                          + * Locks are capable of detecting and recovering from programming errors that cause + * circular waiting deadlocks. When a deadlock between two or more ILock + * instances is detected, detailed debugging information is printed to the log file. The + * locks will then automatically recover from the deadlock by employing a release + * and wait strategy. One thread will lose control of the locks it owns, thus breaking + * the deadlock and allowing other threads to proceed. Once that thread's locks are + * all available, it will be given exclusive access to all its locks and allowed to proceed. + * A thread can only lose locks while it is waiting on an acquire() call. + * + *

                          + * Successive acquire attempts by different threads are queued and serviced on + * a first come, first served basis. + *

                          + * It is very important that acquired locks eventually get released. Calls to release + * should be done in a finally block to ensure they execute. For example: + *

                          + * try {
                          + * 	lock.acquire();
                          + * 	// ... do work here ...
                          + * } finally {
                          + * 	lock.release();
                          + * }
                          + * 
                          + * Note: although lock.acquire should never fail, it is good practice to place + * it inside the try block anyway. Releasing without acquiring is far less catastrophic + * than acquiring without releasing. + *

                          + * + * @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/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ISchedulingRule.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java new file mode 100644 index 0000000000..2b43a7bea2 --- /dev/null +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java @@ -0,0 +1,944 @@ +/******************************************************************************* + * 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 + * 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) { + try { + return function.run(monitor); + } finally { + monitor.done(); + } + } + }; + } + + /** + * Creates a new Job that will execute the provided runnable when it runs. + * + * @param name + * the name of the job + * @param runnable + * the runnable to execute + * @return a job that encapsulates the provided runnable + * @see ICoreRunnable + * @since 3.8 + */ + public static Job create(String name, final ICoreRunnable runnable) { + return new Job(name) { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + runnable.run(monitor); + } catch (CoreException e) { + IStatus st = e.getStatus(); + return new Status(st.getSeverity(), st.getPlugin(), st.getCode(), st.getMessage(), e); + } finally { + monitor.done(); + } + return Status.OK_STATUS; + } + }; + } + + /** + * Creates a new system {@link Job} with the given name that will execute + * the provided runnable when it runs. + * + * @param name + * the name of the job + * @param runnable + * the runnable to execute + * @return a job that encapsulates the provided runnable + * @see ICoreRunnable + * @see Job#setSystem(boolean) + * @since 3.8 + */ + public static Job createSystem(String name, final ICoreRunnable runnable) { + Job job = create(name, runnable); + job.setSystem(true); + return job; + } + + /** + * Creates a new system {@link Job} that will execute the provided runnable + * when it runs. + * + * @param runnable + * the runnable to execute + * @return a job that encapsulates the provided runnable + * @see ICoreRunnable + * @see Job#setSystem(boolean) + * @since 3.8 + */ + public static Job createSystem(final ICoreRunnable runnable) { + return createSystem("", runnable); //$NON-NLS-1$ + } + + /** + * 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/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobChangeAdapter.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobGroup.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/LockListener.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/MultiRule.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/MultiRule.java new file mode 100644 index 0000000000..50c8bd58c3 --- /dev/null +++ b/third_party/patches/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ProgressProvider.java b/third_party/patches/neon/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/neon/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/neon/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/package.html b/third_party/patches/neon/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/neon/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. +

                          +@since 3.0 +

                          + + diff --git a/third_party/patches/neon/org.eclipse.core.resources/META-INF/MANIFEST.MF b/third_party/patches/neon/org.eclipse.core.resources/META-INF/MANIFEST.MF index 851f42d282..85cfb1829c 100644 --- a/third_party/patches/neon/org.eclipse.core.resources/META-INF/MANIFEST.MF +++ b/third_party/patches/neon/org.eclipse.core.resources/META-INF/MANIFEST.MF @@ -1,6 +1,6 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 -Bundle-Name: %pluginName +Bundle-Name: org.eclipse.core.resources patched for bug X Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true Bundle-Version: 3.11.1.v20161107-2032 Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin diff --git a/third_party/patches/neon/pom.xml b/third_party/patches/neon/pom.xml index 1e09544d8c..3807d9fe90 100644 --- a/third_party/patches/neon/pom.xml +++ b/third_party/patches/neon/pom.xml @@ -16,6 +16,7 @@ Patches bundles for Eclipse Neon - org.eclipse.core.resources + + org.eclipse.core.jobs diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/.classpath b/third_party/patches/oxygen/org.eclipse.core.jobs/.classpath new file mode 100644 index 0000000000..4f83b2397e --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/.options b/third_party/patches/oxygen/org.eclipse.core.jobs/.options new file mode 100644 index 0000000000..801e8226eb --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.jobs/.project b/third_party/patches/oxygen/org.eclipse.core.jobs/.project new file mode 100644 index 0000000000..eac5e6eeee --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.core.resources.prefs b/third_party/patches/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000000..99f26c0203 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.core.runtime.prefs b/third_party/patches/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000000..5a0ad22d2a --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs b/third_party/patches/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..34536f44c3 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,403 @@ +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.nonnull.secondary= +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullable.secondary= +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.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +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.nonnullTypeVariableFromLegacyInvocation=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.pessimisticNullAnalysisForFreeTypeVariables=warning +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=error +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.8 +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/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.jdt.ui.prefs b/third_party/patches/oxygen/org.eclipse.core.jobs/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000000..e8333bda65 --- /dev/null +++ b/third_party/patches/oxygen/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= +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/oxygen/org.eclipse.core.jobs/META-INF/MANIFEST.MF b/third_party/patches/oxygen/org.eclipse.core.jobs/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..782b367148 --- /dev/null +++ b/third_party/patches/oxygen/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.9.1.qualifier +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.8.0,4.0.0)" +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +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/oxygen/org.eclipse.core.jobs/about.html b/third_party/patches/oxygen/org.eclipse.core.jobs/about.html new file mode 100644 index 0000000000..460233046e --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.jobs/build.properties b/third_party/patches/oxygen/org.eclipse.core.jobs/build.properties new file mode 100644 index 0000000000..0fe03aa0ed --- /dev/null +++ b/third_party/patches/oxygen/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.8 \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/forceQualifierUpdate.txt b/third_party/patches/oxygen/org.eclipse.core.jobs/forceQualifierUpdate.txt new file mode 100644 index 0000000000..56f1032a8a --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.jobs/plugin.properties b/third_party/patches/oxygen/org.eclipse.core.jobs/plugin.properties new file mode 100644 index 0000000000..834b35fd15 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.jobs/plugin.xml b/third_party/patches/oxygen/org.eclipse.core.jobs/plugin.xml new file mode 100644 index 0000000000..60fd36db0e --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/plugin.xml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/pom.xml b/third_party/patches/oxygen/org.eclipse.core.jobs/pom.xml new file mode 100644 index 0000000000..70d888f2be --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/pom.xml @@ -0,0 +1,28 @@ + + + + 4.0.0 + + eclipse.platform.runtime + eclipse.platform.runtime + 4.7.1-SNAPSHOT + ../../ + + org.eclipse.core + org.eclipse.core.jobs + 3.9.1-SNAPSHOT + eclipse-plugin + + -warn:-deprecation,raw,unchecked + + diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Counter.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Deadlock.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/DeadlockDetector.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/DeadlockDetector.java new file mode 100644 index 0000000000..e8eba826eb --- /dev/null +++ b/third_party/patches/oxygen/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 (Thread element : blocking) { + //if we have already visited the given thread, then we found a cycle + if (deadlockedThreads.contains(element)) { + inCycle = true; + } else { + //otherwise, add the thread to our list and recurse deeper + deadlockedThreads.add(element); + //if the thread is not part of a cycle, remove it from the list + if (addCycleThreads(deadlockedThreads, element)) + inCycle = true; + else + deadlockedThreads.remove(element); + } + } + 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 (ISchedulingRule element : locksToSuspend) + setToWait(deadlock.getCandidate(), element, 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[] element : graph) { + if (emptyColumns[j] && (element[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 (Thread thread : threads) { + Object[] ownedLocks = getOwnedLocks(thread); + Object waitLock = getWaitingLock(thread); + StringBuffer buf = new StringBuffer("Thread "); //$NON-NLS-1$ + buf.append(thread.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 (Thread candidate : candidates) { + if (ownsRealLocks(candidate)) + return candidate; + } + //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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ImplicitJobs.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ImplicitJobs.java new file mode 100644 index 0000000000..70c5d1b596 --- /dev/null +++ b/third_party/patches/oxygen/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 (ISchedulingRule iSchedulingRule : suspendedRules) + if (iSchedulingRule.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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJob.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJob.java new file mode 100644 index 0000000000..167334cbff --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJobGroup.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJobGroup.java new file mode 100644 index 0000000000..ad109aca01 --- /dev/null +++ b/third_party/patches/oxygen/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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalWorker.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java new file mode 100644 index 0000000000..f5655829e7 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobListeners.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * 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 = IJobChangeListener::aboutToRun; + private final IListenerDoit awake = IJobChangeListener::awake; + private final IListenerDoit done = IJobChangeListener::done; + private final IListenerDoit running = IJobChangeListener::running; + private final IListenerDoit scheduled = IJobChangeListener::scheduled; + private final IListenerDoit sleeping = IJobChangeListener::sleeping; + + /** + * 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 + for (IJobChangeListener listener : global) { + try { + doit.notify(listener, event); + } catch (Throwable e) { + handleException(listener, e); + } + } + for (IJobChangeListener listener : ((InternalJob) event.getJob()).getListeners()) { + try { + doit.notify(listener, event); + } catch (Throwable e) { + handleException(listener, e); + } + } + } + + private void handleException(IJobChangeListener 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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java new file mode 100644 index 0000000000..4015b9c964 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java @@ -0,0 +1,1802 @@ +/******************************************************************************* + * 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 monitorStack = new ArrayList<>(); + + private final InternalWorker internalWorker; + + public static void debug(String msg) { + StringBuffer msgBuf = new StringBuffer(msg.length() + 40); + if (DEBUG_TIMING) { + //lazy initialize to avoid overhead when not debugging + if (DEBUG_FORMAT == null) + DEBUG_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"); //$NON-NLS-1$ + DEBUG_FORMAT.format(new Date(), msgBuf, new FieldPosition(0)); + msgBuf.append('-'); + } + msgBuf.append('[').append(Thread.currentThread()).append(']').append(msg); + System.out.println(msgBuf.toString()); + } + + /** + * Returns the job manager singleton. For internal use only. + */ + static synchronized JobManager getInstance() { + if (instance == null) + new JobManager(); + return instance; + } + + /** + * For debugging purposes only + */ + private static String printJobName(Job job) { + if (job instanceof ThreadJob) { + Job realJob = ((ThreadJob) job).realJob; + if (realJob != null) + return realJob.getClass().getName(); + return "ThreadJob on rule: " + job.getRule(); //$NON-NLS-1$ + } + return job.getClass().getName(); + } + + /** + * For debugging purposes only + */ + public static String printState(Job job) { + return printState(((InternalJob) job).internalGetState()); + } + + /** + * For debugging purposes only + */ + public static String printState(int state) { + switch (state) { + case Job.NONE : + return "NONE"; //$NON-NLS-1$ + case Job.WAITING : + return "WAITING"; //$NON-NLS-1$ + case Job.SLEEPING : + return "SLEEPING"; //$NON-NLS-1$ + case Job.RUNNING : + return "RUNNING"; //$NON-NLS-1$ + case InternalJob.BLOCKED : + return "BLOCKED"; //$NON-NLS-1$ + case InternalJob.YIELDING : + return "YIELDING"; //$NON-NLS-1$ + case InternalJob.ABOUT_TO_RUN : + return "ABOUT_TO_RUN"; //$NON-NLS-1$ + case InternalJob.ABOUT_TO_SCHEDULE : + return "ABOUT_TO_SCHEDULE";//$NON-NLS-1$ + } + return "UNKNOWN"; //$NON-NLS-1$ + } + + /** + * Note that although this method is not API, clients have historically used + * it to force jobs shutdown in cases where OSGi shutdown does not occur. + * For this reason, this method should be considered near-API and should not + * be changed if at all possible. + */ + public static void shutdown() { + if (instance != null) { + instance.doShutdown(); + instance = null; + } + } + + private JobManager() { + instance = this; + synchronized (lock) { + waiting = new JobQueue(false); + waitingThreadJobs = new JobQueue(false, false); + sleeping = new JobQueue(true); + running = new HashSet<>(10); + yielding = new HashSet<>(10); + pool = new WorkerPool(this); + } + pool.setDaemon(JobOSGiUtils.getDefault().useDaemonThreads()); + internalWorker = new InternalWorker(this); + internalWorker.setDaemon(JobOSGiUtils.getDefault().useDaemonThreads()); + internalWorker.start(); + jobListeners.add(jobGroupUpdater); + } + + @Override + public void addJobChangeListener(IJobChangeListener listener) { + jobListeners.add(listener); + } + + @Override + public void beginRule(ISchedulingRule rule, IProgressMonitor monitor) { + validateRule(rule); + implicitJobs.begin(rule, monitorFor(monitor), false); + } + + /** + * Cancels a job + */ + protected boolean cancel(InternalJob job) { + IProgressMonitor monitor = null; + boolean runCanceling = false; + synchronized (lock) { + switch (job.getState()) { + case Job.NONE : + return true; + case Job.RUNNING : + //cannot cancel a job that has already started (as opposed to ABOUT_TO_RUN) + if (job.internalGetState() == Job.RUNNING) { + monitor = job.getProgressMonitor(); + runCanceling = !job.isRunCanceled(); + if (runCanceling) + job.setRunCanceled(true); + break; + } + //signal that the job should be canceled before it gets a chance to run + job.setAboutToRunCanceled(true); + return false; + default : + changeState(job, Job.NONE); + } + } + //call monitor and canceling outside sync block + if (monitor != null) { + if (runCanceling) { + if (!monitor.isCanceled()) + monitor.setCanceled(true); + job.canceling(); + } + return false; + } + //only notify listeners if the job was waiting or sleeping + jobListeners.done((Job) job, Status.CANCEL_STATUS, false); + return true; + } + + @Override + public void cancel(Object family) { + //don't synchronize because cancel calls listeners + for (InternalJob internalJob : select(family)) + cancel(internalJob); + } + + void cancel(InternalJobGroup jobGroup) { + cancel(jobGroup, false); + } + + void cancel(InternalJobGroup jobGroup, boolean cancelDueToError) { + Assert.isLegal(jobGroup != null, "jobGroup should not be null"); //$NON-NLS-1$ + synchronized (lock) { + switch (jobGroup.getState()) { + case JobGroup.NONE : + return; + case JobGroup.CANCELING : + if (!cancelDueToError) { + // User cancellation takes precedence over the cancel due to error. + jobGroup.updateCancelingReason(cancelDueToError); + } + return; + default : + jobGroup.cancelAndNotify(cancelDueToError); + } + } + } + + /** + * Atomically updates the state of a job, adding or removing from the + * necessary queues or sets. + */ + private void changeState(InternalJob job, int newState) { + boolean blockedJobs = false; + synchronized (lock) { + int oldJobState; + synchronized (job.jobStateLock) { + job.jobStateLock.notifyAll(); + oldJobState = job.getState(); + int oldState = job.internalGetState(); + switch (oldState) { + case InternalJob.YIELDING : + yielding.remove(job); + case Job.NONE : + case InternalJob.ABOUT_TO_SCHEDULE : + break; + case InternalJob.BLOCKED : + //remove this job from the linked list of blocked jobs + job.remove(); + break; + case Job.WAITING : + try { + waiting.remove(job); + } catch (RuntimeException e) { + Assert.isLegal(false, "Tried to remove a job that wasn't in the queue"); //$NON-NLS-1$ + } + break; + case Job.SLEEPING : + try { + sleeping.remove(job); + } catch (RuntimeException e) { + Assert.isLegal(false, "Tried to remove a job that wasn't in the queue"); //$NON-NLS-1$ + } + break; + case Job.RUNNING : + case InternalJob.ABOUT_TO_RUN : + running.remove(job); + //add any blocked jobs back to the wait queue + InternalJob blocked = job.previous(); + job.remove(); + blockedJobs = blocked != null; + while (blocked != null) { + InternalJob previous = blocked.previous(); + changeState(blocked, Job.WAITING); + blocked = previous; + } + break; + default : + Assert.isLegal(false, "Invalid job state: " + job + ", state: " + oldState); //$NON-NLS-1$ //$NON-NLS-2$ + } + job.internalSetState(newState); + switch (newState) { + case Job.NONE : + job.setStartTime(InternalJob.T_NONE); + job.setWaitQueueStamp(InternalJob.T_NONE); + job.setRunCanceled(false); + case InternalJob.BLOCKED : + break; + case Job.WAITING : + waiting.enqueue(job); + break; + case Job.SLEEPING : + try { + sleeping.enqueue(job); + } catch (RuntimeException e) { + throw new RuntimeException("Error changing from state: " + oldState); //$NON-NLS-1$ + } + break; + case Job.RUNNING : + case InternalJob.ABOUT_TO_RUN : + // These flags must be reset in all cases, including resuming from yield + job.setStartTime(InternalJob.T_NONE); + job.setWaitQueueStamp(InternalJob.T_NONE); + running.add(job); + break; + case InternalJob.YIELDING : + yielding.add(job); + case InternalJob.ABOUT_TO_SCHEDULE : + break; + default : + Assert.isLegal(false, "Invalid job state: " + job + ", state: " + newState); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + InternalJobGroup jobGroup = job.getJobGroup(); + if (jobGroup != null) { + jobGroup.jobStateChanged(job, oldJobState, job.getState()); + } + } + + //notify queue outside sync block + if (blockedJobs) + pool.jobQueued(); + } + + /** + * Returns a new progress monitor for this job, belonging to the given + * progress group. Returns null if it is not a valid time to set the job's group. + */ + protected IProgressMonitor createMonitor(InternalJob job, IProgressMonitor group, int ticks) { + synchronized (lock) { + //group must be set before the job is scheduled + //this includes the ABOUT_TO_SCHEDULE state, during which it is still + //valid to set the progress monitor + if (job.getState() != Job.NONE) + return null; + IProgressMonitor monitor = null; + if (progressProvider != null) + monitor = progressProvider.createMonitor((Job) job, group, ticks); + if (monitor == null) + monitor = new NullProgressMonitor(); + return monitor; + } + } + + /** + * Returns a new progress monitor for this job. Never returns null. + * @GuardedBy("lock") + */ + private IProgressMonitor createMonitor(Job job) { + IProgressMonitor monitor = null; + if (progressProvider != null) + monitor = progressProvider.createMonitor(job); + if (monitor == null) + monitor = new NullProgressMonitor(); + return monitor; + } + + @Override + public IProgressMonitor createProgressGroup() { + if (progressProvider != null) + return progressProvider.createProgressGroup(); + return new NullProgressMonitor(); + } + + @Override + public Job currentJob() { + Thread current = Thread.currentThread(); + if (current instanceof Worker) + return ((Worker) current).currentJob(); + synchronized (lock) { + for (InternalJob internalJob : running) { + Job job = (Job) internalJob; + if (job.getThread() == current) + return job; + } + } + return null; + } + + @Override + public ISchedulingRule currentRule() { + //check thread job first, because actual current job may have null rule + Job currentJob = implicitJobs.getThreadJob(Thread.currentThread()); + if (currentJob != null) + return currentJob.getRule(); + currentJob = currentJob(); + if (currentJob != null) + return currentJob.getRule(); + return null; + } + + /** + * Returns the delay in milliseconds that a job with a given priority can + * tolerate waiting. + */ + private long delayFor(int priority) { + //these values may need to be tweaked based on machine speed + switch (priority) { + case Job.INTERACTIVE : + return 0L; + case Job.SHORT : + return 50L; + case Job.LONG : + return 100L; + case Job.BUILD : + return 500L; + case Job.DECORATE : + return 1000L; + default : + Assert.isTrue(false, "Job has invalid priority: " + priority); //$NON-NLS-1$ + return 0; + } + } + + /** + * Performs the scheduling of a job. Does not perform any notifications. + */ + private void doSchedule(InternalJob job, long delay) { + synchronized (lock) { + //job may have been canceled already + int state = job.internalGetState(); + if (state != InternalJob.ABOUT_TO_SCHEDULE && state != Job.SLEEPING) + return; + //if it's a decoration job with no rule, don't run it right now if the system is busy + if (job.getPriority() == Job.DECORATE && job.getRule() == null) { + long minDelay = running.size() * 100; + delay = Math.max(delay, minDelay); + } + if (delay > 0) { + job.setStartTime(System.currentTimeMillis() + delay); + changeState(job, Job.SLEEPING); + } else { + job.setStartTime(System.currentTimeMillis() + delayFor(job.getPriority())); + job.setWaitQueueStamp(waitQueueCounter.increment()); + changeState(job, Job.WAITING); + } + } + } + + /** + * Shuts down the job manager. Currently running jobs will be told + * to stop, but worker threads may still continue processing. + * (note: This implemented IJobManager.shutdown which was removed + * due to problems caused by premature shutdown) + */ + private void doShutdown() { + Job[] toCancel = null; + synchronized (lock) { + if (!active) + return; + active = false; + //cancel all running jobs + toCancel = running.toArray(new Job[running.size()]); + //discard any jobs that have not yet started running + sleeping.clear(); + waiting.clear(); + } + + // Give running jobs a chance to finish. Wait 0.1 seconds for up to 3 times. + if (toCancel != null && toCancel.length > 0) { + for (Job element : toCancel) { + cancel(element); // cancel jobs outside sync block to avoid deadlock + } + + for (int waitAttempts = 0; waitAttempts < 3; waitAttempts++) { + Thread.yield(); + synchronized (lock) { + if (running.isEmpty()) + break; + } + if (DEBUG_SHUTDOWN) { + JobManager.debug("Shutdown - job wait cycle #" + (waitAttempts + 1)); //$NON-NLS-1$ + Job[] stillRunning = null; + synchronized (lock) { + stillRunning = running.toArray(new Job[running.size()]); + } + if (stillRunning != null) { + for (Job element : stillRunning) { + JobManager.debug("\tJob: " + printJobName(element)); //$NON-NLS-1$ + } + } + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + //ignore + } + Thread.yield(); + } + + synchronized (lock) { // retrieve list of the jobs that are still running + toCancel = running.toArray(new Job[running.size()]); + } + } + internalWorker.cancel(); + if (toCancel != null) { + for (Job element : toCancel) { + String jobName = printJobName(element); + //this doesn't need to be translated because it's just being logged + String msg = "Job found still running after platform shutdown. Jobs should be canceled by the plugin that scheduled them during shutdown: " + jobName; //$NON-NLS-1$ + RuntimeLog.log(new Status(IStatus.WARNING, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, null)); + + // TODO the RuntimeLog.log in its current implementation won't produce a log + // during this stage of shutdown. For now add a standard error output. + // One the logging story is improved, the System.err output below can be removed: + System.err.println(msg); + } + } + synchronized (lock) { + //discard reference to any jobs still running at this point + running.clear(); + } + + pool.shutdown(); + jobListeners.remove(jobGroupUpdater); + } + + /** + * Indicates that a job was running, and has now finished. Note that this method + * can be called under OutOfMemoryError conditions and thus must be paranoid + * about allocating objects. + */ + protected void endJob(InternalJob job, IStatus result, boolean notify) { + long rescheduleDelay = InternalJob.T_NONE; + synchronized (lock) { + //if the job is finishing asynchronously, there is nothing more to do for now + if (result == Job.ASYNC_FINISH) + return; + //if job is not known then it cannot be done + if (job.getState() == Job.NONE) + return; + if (JobManager.DEBUG && notify) + JobManager.debug("Ending job: " + job); //$NON-NLS-1$ + job.setResult(result); + job.setProgressMonitor(null); + job.setThread(null); + rescheduleDelay = job.getStartTime(); + changeState(job, Job.NONE); + } + //notify listeners outside sync block + final boolean reschedule = active && rescheduleDelay > InternalJob.T_NONE && job.shouldSchedule(); + if (notify) + jobListeners.done((Job) job, result, reschedule); + //reschedule the job if requested and we are still active + if (reschedule) + schedule(job, rescheduleDelay, reschedule); + //log result if it is warning or error. When the job belongs to a job group defer the logging + //until the whole group is completed (see JobManager#updateJobGroup). + if (job.getJobGroup() == null && result.matches(IStatus.ERROR | IStatus.WARNING)) + RuntimeLog.log(result); + } + + @Override + public void endRule(ISchedulingRule rule) { + implicitJobs.end(rule, false); + } + + @Override + public Job[] find(Object family) { + List members = select(family); + return members.toArray(new Job[members.size()]); + } + + List find(InternalJobGroup jobGroup) { + Assert.isLegal(jobGroup != null, "jobGroup should not be null"); //$NON-NLS-1$ + synchronized (lock) { + return jobGroup.internalGetActiveJobs(); + } + } + + /** + * Returns a running or blocked job whose scheduling rule conflicts with the + * scheduling rule of the given waiting job. Returns null if there are no + * conflicting jobs. A job can only run if there are no running jobs and no blocked + * jobs whose scheduling rule conflicts with its rule. + */ + protected InternalJob findBlockingJob(InternalJob waitingJob) { + if (waitingJob.getRule() == null) + return null; + synchronized (lock) { + if (running.isEmpty()) + return null; + //check the running jobs + boolean hasBlockedJobs = false; + for (InternalJob job : running) { + if (waitingJob.isConflicting(job)) + return job; + if (!hasBlockedJobs) + hasBlockedJobs = job.previous() != null; + } + //there are no blocked jobs, so we are done + if (!hasBlockedJobs) + return null; + //check all jobs blocked by running jobs + for (InternalJob job : running) { + while (true) { + job = job.previous(); + if (job == null) + break; + if (waitingJob.isConflicting(job)) + return job; + } + } + } + return null; + } + + /** + * Returns a job from the given collection whose scheduling rule conflicts + * with the scheduling rule of the given job. Returns null if there are no + * conflicting jobs. + */ + InternalJob findBlockedJob(InternalJob job, Iterator jobs) { + synchronized (lock) { + while (jobs.hasNext()) { + InternalJob waitingJob = (InternalJob) jobs.next(); + if (waitingJob.isConflicting(job)) + return waitingJob; + } + return null; + } + } + + void dequeue(JobQueue queue, InternalJob job) { + synchronized (lock) { + queue.remove(job); + } + } + + void enqueue(JobQueue queue, InternalJob job) { + synchronized (lock) { + queue.enqueue(job); + } + } + + public LockManager getLockManager() { + return lockManager; + } + + /** + * Returns a translated message indicating we are waiting for the given + * number of jobs to complete. + */ + private String getWaitMessage(int jobCount) { + String message = jobCount == 1 ? JobMessages.jobs_waitFamSubOne : JobMessages.jobs_waitFamSub; + return NLS.bind(message, Integer.toString(jobCount)); + } + + /** + * Returns whether the job manager is active (has not been shutdown). + */ + protected boolean isActive() { + return active; + } + + /** + * Returns true if the given job is blocking the execution of a non-system + * job. + */ + protected boolean isBlocking(InternalJob runningJob) { + synchronized (lock) { + // if this job isn't running, it can't be blocking anyone + if (runningJob.getState() != Job.RUNNING) + return false; + // if any job is queued behind this one, it is blocked by it + InternalJob previous = runningJob.previous(); + while (previous != null) { + // ignore jobs of lower priority (higher priority value means lower priority) + if (previous.getPriority() < runningJob.getPriority()) { + if (!previous.isSystem()) + return true; + // implicit jobs should interrupt unless they act on behalf of system jobs + if (previous instanceof ThreadJob && ((ThreadJob) previous).shouldInterrupt()) + return true; + } + previous = previous.previous(); + } + // consider threads waiting on IJobManager#beginRule + for (Iterator i = waitingThreadJobs.iterator(); i.hasNext();) { + ThreadJob waitingJob = (ThreadJob) i.next(); + if (runningJob.isConflicting(waitingJob) && waitingJob.shouldInterrupt()) + return true; + } + // none found + return false; + } + } + + @Override + public boolean isIdle() { + synchronized (lock) { + return running.isEmpty() && waiting.isEmpty(); + } + } + + @Override + public boolean isSuspended() { + synchronized (lock) { + return suspended; + } + } + + protected boolean join(InternalJob job, long timeout, IProgressMonitor monitor) throws InterruptedException { + Assert.isLegal(timeout >= 0, "timeout should not be negative"); //$NON-NLS-1$ + long deadline = timeout == 0 ? 0 : System.currentTimeMillis() + timeout; + + Job currentJob = currentJob(); + if (currentJob != null) { + JobGroup jobGroup = currentJob.getJobGroup(); + if (timeout == 0 && jobGroup != null && jobGroup.getMaxThreads() != 0 && jobGroup == job.getJobGroup()) + throw new IllegalStateException("Joining on a job belonging to the same group is not allowed"); //$NON-NLS-1$ + } + + final IJobChangeListener listener; + final Semaphore barrier; + synchronized (lock) { + int state = job.getState(); + if (state == Job.NONE) + return true; + //don't join a waiting or sleeping job when suspended (deadlock risk) + if (suspended && state != Job.RUNNING) + return true; + //it's an error for a job to join itself + if (state == Job.RUNNING && job.getThread() == Thread.currentThread()) + throw new IllegalStateException("Job attempted to join itself"); //$NON-NLS-1$ + //the semaphore will be released when the job is done + barrier = new Semaphore(null); + listener = new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + barrier.release(); + } + }; + job.addJobChangeListener(listener); + } + + //wait until listener notifies this thread. + try { + boolean canBlock = lockManager.canBlock(); + while (true) { + if (monitor != null && monitor.isCanceled()) + throw new OperationCanceledException(); + long remainingTime = deadline; + if (deadline != 0) { + remainingTime -= System.currentTimeMillis(); + if (remainingTime <= 0) { + return false; + } + } + //notify hook to service pending syncExecs before falling asleep + lockManager.aboutToWait(job.getThread()); + try { + // 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; + if (barrier.acquire(sleepTime)) + break; + } catch (InterruptedException e) { + // if non-UI thread, re-throw the exception + if (canBlock) + throw e; + // if UI thread, loop and keep trying + } + } + } finally { + lockManager.aboutToRelease(); + job.removeJobChangeListener(listener); + } + return true; + } + + @Override + public void join(final Object family, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException { + monitor = monitorFor(monitor); + IJobChangeListener listener = null; + final Set jobs; + int jobCount; + Job blocking = null; + synchronized (lock) { + //don't join a waiting or sleeping job when suspended (deadlock risk) + int states = suspended ? Job.RUNNING : Job.RUNNING | Job.WAITING | Job.SLEEPING; + jobs = Collections.synchronizedSet(new HashSet<>(select(family, states))); + jobCount = jobs.size(); + if (jobCount > 0) { + //if there is only one blocking job, use it in the blockage callback below + if (jobCount == 1) + blocking = (Job) jobs.iterator().next(); + listener = new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + //don't remove from list if job is being rescheduled + if (!((JobChangeEvent) event).reschedule) + jobs.remove(event.getJob()); + } + + //update the list of jobs if new ones are started during the join + @Override + public void running(IJobChangeEvent event) { + Job job = event.getJob(); + if (family == null || job.belongsTo(family)) + jobs.add(job); + } + + //update the list of jobs if new ones are scheduled during the join + @Override + public void scheduled(IJobChangeEvent event) { + //don't add to list if job is being rescheduled + if (((JobChangeEvent) event).reschedule) + return; + //if job manager is suspended we only wait for running jobs + if (isSuspended()) + return; + Job job = event.getJob(); + if (family == null || job.belongsTo(family)) + jobs.add(job); + } + }; + addJobChangeListener(listener); + } + } + if (jobCount == 0) { + //use up the monitor outside synchronized block because monitors call untrusted code + monitor.beginTask(JobMessages.jobs_blocked0, 1); + monitor.done(); + return; + } + //spin until all jobs are completed + try { + monitor.beginTask(JobMessages.jobs_blocked0, jobCount); + monitor.subTask(getWaitMessage(jobCount)); + reportBlocked(monitor, blocking); + int jobsLeft; + int reportedWorkDone = 0; + while ((jobsLeft = jobs.size()) > 0) { + //don't let there be negative work done if new jobs have + //been added since the join began + int actualWorkDone = Math.max(0, jobCount - jobsLeft); + if (reportedWorkDone < actualWorkDone) { + monitor.worked(actualWorkDone - reportedWorkDone); + reportedWorkDone = actualWorkDone; + monitor.subTask(getWaitMessage(jobsLeft)); + } + if (Thread.interrupted()) + throw new InterruptedException(); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + //notify hook to service pending syncExecs before falling asleep + lockManager.aboutToWait(null); + Thread.sleep(100); + } + } finally { + lockManager.aboutToRelease(); + removeJobChangeListener(listener); + reportUnblocked(monitor); + monitor.done(); + } + } + + boolean join(InternalJobGroup jobGroup, long timeout, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException { + Assert.isLegal(jobGroup != null, "jobGroup should not be null"); //$NON-NLS-1$ + Assert.isLegal(timeout >= 0, "timeout should not be negative"); //$NON-NLS-1$ + long deadline = timeout == 0 ? 0 : System.currentTimeMillis() + timeout; + int jobCount; + synchronized (lock) { + jobCount = jobGroup.getActiveJobsCount(); + } + + SubMonitor subMonitor = SubMonitor.convert(monitor, JobMessages.jobs_blocked0, jobCount); + try { + while (true) { + if (subMonitor.isCanceled()) + throw new OperationCanceledException(); + long remainingTime = deadline; + if (deadline != 0) { + remainingTime -= System.currentTimeMillis(); + if (remainingTime <= 0) { + return false; + } + } + synchronized (lock) { + if ((suspended && jobGroup.getRunningJobsCount() == 0)) + break; + } + if (jobGroup.doJoin(remainingTime)) + break; + int jobsLeft; + synchronized (lock) { + jobsLeft = jobGroup.getActiveJobsCount(); + } + if (jobsLeft < jobCount) + subMonitor.worked(jobCount - jobsLeft); + + jobCount = jobsLeft; + subMonitor.setWorkRemaining(jobCount); + subMonitor.subTask(getWaitMessage(jobCount)); + } + } finally { + if (monitor != null) { + monitor.done(); + } + } + return true; + } + + /** + * Returns a non-null progress monitor instance. If the monitor is null, + * returns the default monitor supplied by the progress provider, or a + * NullProgressMonitor if no default monitor is available. + */ + private IProgressMonitor monitorFor(IProgressMonitor monitor) { + if (monitor == null || (monitor instanceof NullProgressMonitor)) { + if (progressProvider != null) { + try { + monitor = progressProvider.getDefaultMonitor(); + } catch (Exception e) { + String msg = NLS.bind(JobMessages.meta_pluginProblems, JobManager.PI_JOBS); + RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, e)); + } + } + } + + if (monitor == null) + return new NullProgressMonitor(); + return monitor; + } + + @Override + public ILock newLock() { + return lockManager.newLock(); + } + + /** + * Removes and returns the first waiting job in the queue which is ready to run. + * Returns null if there are no items waiting in the queue. If an item is + * removed from the queue, it is moved to the running jobs list. + */ + private Job nextJob() { + synchronized (lock) { + // do nothing if the job manager is suspended + if (suspended) + return null; + // tickle the sleep queue to see if anyone wakes up + long now = System.currentTimeMillis(); + InternalJob job = sleeping.peek(); + while (job != null && job.getStartTime() < now) { + job.setStartTime(now + delayFor(job.getPriority())); + job.setWaitQueueStamp(waitQueueCounter.increment()); + changeState(job, Job.WAITING); + job = sleeping.peek(); + } + InternalJobGroup jobGroup = null; + // process the wait queue until we find a job whose rules are satisfied. + job = waiting.peek(); + while (job != null) { + InternalJob blocker = findBlockingJob(job); + jobGroup = job.getJobGroup(); + // previous() method returns the next job in the queue. + InternalJob nextWaitingJob = job.previous(); + if (blocker != null) { + // queue this job after the job that's blocking it + changeState(job, InternalJob.BLOCKED); + // assert job does not already belong to some other data structure + Assert.isTrue(job.next() == null); + Assert.isTrue(job.previous() == null); + blocker.addLast(job); + + } else if (jobGroup == null || jobGroup.getMaxThreads() == 0 || (jobGroup.getState() != JobGroup.CANCELING && jobGroup.getRunningJobsCount() < jobGroup.getMaxThreads())) { + break; + } + // skip this job as either this job is blocked on another job or + // the maximum number of jobs from the same group are already running. + job = nextWaitingJob == waiting.dummy ? null : nextWaitingJob; + } + // the job to run must be in the running list before we exit + // the sync block, otherwise two jobs with conflicting rules could start at once + if (job != null) { + changeState(job, InternalJob.ABOUT_TO_RUN); + if (JobManager.DEBUG) + JobManager.debug("Starting job: " + job); //$NON-NLS-1$ + } + return (Job) job; + } + } + + @Override + public void optionsChanged(DebugOptions options) { + DEBUG = options.getBooleanOption(OPTION_DEBUG_JOBS, false); + DEBUG_BEGIN_END = options.getBooleanOption(OPTION_DEBUG_BEGIN_END, false); + DEBUG_YIELDING = options.getBooleanOption(OPTION_DEBUG_YIELDING, false); + DEBUG_YIELDING_DETAILED = options.getBooleanOption(OPTION_DEBUG_YIELDING_DETAILED, false); + DEBUG_DEADLOCK = options.getBooleanOption(OPTION_DEADLOCK_ERROR, false); + DEBUG_LOCKS = options.getBooleanOption(OPTION_LOCKS, false); + DEBUG_TIMING = options.getBooleanOption(OPTION_DEBUG_JOBS_TIMING, false); + DEBUG_SHUTDOWN = options.getBooleanOption(OPTION_SHUTDOWN, false); + } + + @Override + public void removeJobChangeListener(IJobChangeListener listener) { + jobListeners.remove(listener); + } + + /** + * Report to the progress monitor that this thread is blocked, supplying + * an information message, and if possible the job that is causing the blockage. + * Important: An invocation of this method MUST be followed eventually be + * an invocation of reportUnblocked. + * @param monitor The monitor to report blocking to + * @param blockingJob The job that is blocking this thread, or null + * @see #reportUnblocked + */ + final void reportBlocked(IProgressMonitor monitor, InternalJob blockingJob) { + if (!(monitor instanceof IProgressMonitorWithBlocking)) + return; + IStatus reason; + if (blockingJob == null || blockingJob instanceof ThreadJob || blockingJob.isSystem()) { + reason = new Status(IStatus.INFO, JobManager.PI_JOBS, 1, JobMessages.jobs_blocked0, null); + } else { + String msg = NLS.bind(JobMessages.jobs_blocked1, blockingJob.getName()); + reason = new JobStatus(IStatus.INFO, (Job) blockingJob, msg); + } + ((IProgressMonitorWithBlocking) monitor).setBlocked(reason); + } + + /** + * Reports that this thread was blocked, but is no longer blocked and is able + * to proceed. + * @param monitor The monitor to report unblocking to. + * @see #reportBlocked + */ + final void reportUnblocked(IProgressMonitor monitor) { + if (monitor instanceof IProgressMonitorWithBlocking) + ((IProgressMonitorWithBlocking) monitor).clearBlocked(); + } + + @Override + public final void resume() { + synchronized (lock) { + suspended = false; + //poke the job pool + pool.jobQueued(); + } + } + + @Deprecated + @Override + public final void resume(ISchedulingRule rule) { + implicitJobs.resume(rule); + } + + /** + * Attempts to immediately start a given job. Returns null if the job was + * successfully started, and the blocking job if it could not be started immediately + * due to a currently running job with a conflicting rule. Listeners will never + * be notified of jobs that are run in this way. + */ + protected InternalJob runNow(ThreadJob job, boolean releaseWaiting) { + if (releaseWaiting) { + synchronized (implicitJobs) { + synchronized (lock) { + return doRunNow(job, releaseWaiting); + } + } + } + synchronized (lock) { + return doRunNow(job, releaseWaiting); + } + } + + private InternalJob doRunNow(ThreadJob job, boolean releaseWaiting) { + InternalJob blocking = findBlockingJob(job); + //cannot start if there is a conflicting job + if (blocking == null) { + changeState(job, Job.RUNNING); + ((InternalJob) job).setProgressMonitor(new NullProgressMonitor()); + job.run(null); + if (releaseWaiting) { + // atomically release waiting + implicitJobs.removeWaiting(job); + } + } + return blocking; + } + + protected void schedule(InternalJob job, long delay, boolean reschedule) { + if (!active) + throw new IllegalStateException("Job manager has been shut down."); //$NON-NLS-1$ + Assert.isNotNull(job, "Job is null"); //$NON-NLS-1$ + Assert.isLegal(delay >= 0, "Scheduling delay is negative"); //$NON-NLS-1$ + synchronized (lock) { + //if the job is already running, set it to be rescheduled when done + if (job.getState() == Job.RUNNING) { + job.setStartTime(delay); + return; + } + //can't schedule a job that is waiting or sleeping + if (job.internalGetState() != Job.NONE) + return; + if (JobManager.DEBUG) + JobManager.debug("Scheduling job: " + job); //$NON-NLS-1$ + //remember that we are about to schedule the job + //to prevent multiple schedule attempts from succeeding (bug 68452) + changeState(job, InternalJob.ABOUT_TO_SCHEDULE); + } + //notify listeners outside sync block + jobListeners.scheduled((Job) job, delay, reschedule); + //schedule the job + doSchedule(job, delay); + //call the pool outside sync block to avoid deadlock + pool.jobQueued(); + } + + /** + * Adds all family members in the list of jobs to the collection + */ + private void select(List members, Object family, InternalJob firstJob, int stateMask) { + if (firstJob == null) + return; + InternalJob job = firstJob; + do { + //note that job state cannot be NONE at this point + if ((family == null || job.belongsTo(family)) && ((job.getState() & stateMask) != 0)) + members.add(job); + job = job.previous(); + } while (job != null && job != firstJob); + } + + /** + * Returns a list of all jobs known to the job manager that belong to the given family. + */ + private List select(Object family) { + return select(family, Job.WAITING | Job.SLEEPING | Job.RUNNING); + } + + /** + * Returns a list of all jobs known to the job manager that belong to the given + * family and are in one of the provided states. + */ + private List select(Object family, int stateMask) { + List members = new ArrayList<>(); + synchronized (lock) { + if ((stateMask & Job.RUNNING) != 0) { + for (InternalJob internalJob : running) { + select(members, family, internalJob, stateMask); + } + } + if ((stateMask & Job.WAITING) != 0) { + select(members, family, waiting.peek(), stateMask); + for (InternalJob internalJob : yielding) { + select(members, family, internalJob, stateMask); + } + } + if ((stateMask & Job.SLEEPING) != 0) + select(members, family, sleeping.peek(), stateMask); + } + return members; + } + + @Override + public void setLockListener(LockListener listener) { + lockManager.setLockListener(listener); + } + + /** + * Changes a job priority. + */ + protected void setPriority(InternalJob job, int newPriority) { + synchronized (lock) { + int oldPriority = job.getPriority(); + if (oldPriority == newPriority) + return; + job.internalSetPriority(newPriority); + //if the job is waiting to run, re-shuffle the queue + if (job.getState() == Job.WAITING) { + long oldStart = job.getStartTime(); + job.setStartTime(oldStart + (delayFor(newPriority) - delayFor(oldPriority))); + waiting.resort(job); + } + } + } + + @Override + public void setProgressProvider(ProgressProvider provider) { + progressProvider = provider; + } + + public void setRule(InternalJob job, ISchedulingRule rule) { + synchronized (lock) { + //cannot change the rule of a job that is already running + Assert.isLegal(job.getState() == Job.NONE); + validateRule(rule); + job.internalSetRule(rule); + } + } + + /** + * Puts a job to sleep. Returns true if the job was successfully put to sleep. + */ + protected boolean sleep(InternalJob job) { + synchronized (lock) { + switch (job.getState()) { + case Job.RUNNING : + //cannot be paused if it is already running (as opposed to ABOUT_TO_RUN) + if (job.internalGetState() == Job.RUNNING) + return false; + //job hasn't started running yet (aboutToRun listener) + break; + case Job.SLEEPING : + //update the job wake time + job.setStartTime(InternalJob.T_INFINITE); + //change state again to re-shuffle the sleep queue + changeState(job, Job.SLEEPING); + return true; + case Job.NONE : + return true; + case Job.WAITING : + //put the job to sleep + break; + } + job.setStartTime(InternalJob.T_INFINITE); + changeState(job, Job.SLEEPING); + } + jobListeners.sleeping((Job) job); + return true; + } + + @Override + public void sleep(Object family) { + //don't synchronize because sleep calls listeners + for (InternalJob internalJob : select(family)) { + sleep(internalJob); + } + } + + /** + * Returns the estimated time in milliseconds before the next job is scheduled + * to wake up. The result may be negative. Returns InternalJob.T_INFINITE if + * there are no sleeping or waiting jobs. + */ + protected long sleepHint() { + synchronized (lock) { + //wait forever if job manager is suspended + if (suspended) + return InternalJob.T_INFINITE; + if (!waiting.isEmpty()) + return 0L; + //return the anticipated time that the next sleeping job will wake + InternalJob next = sleeping.peek(); + if (next == null) + return InternalJob.T_INFINITE; + return next.getStartTime() - System.currentTimeMillis(); + } + } + + /** + * Implementation of {@link Job#yieldRule(IProgressMonitor)} + */ + protected Job yieldRule(InternalJob job, IProgressMonitor monitor) { + Thread currentThread = Thread.currentThread(); + Assert.isLegal(job.getState() == Job.RUNNING, "Cannot yieldRule job that is " + printState(job.internalGetState())); //$NON-NLS-1$ + Assert.isLegal(currentThread == job.getThread(), "Cannot yieldRule from outside job's thread"); //$NON-NLS-1$ + + InternalJob unblocked; + // If job is not a ThreadJob, and it has implicitly started rules, likeThreadJob + // is the corresponding ThreadJob. Similarly, if likeThreadJob is not null, then + // job is not a ThreadJob + ThreadJob likeThreadJob; + synchronized (implicitJobs) { + synchronized (lock) { + // The nested implicit job, if any + likeThreadJob = implicitJobs.getThreadJob(currentThread); + + unblocked = job.previous(); + + // if unblocked is not null, it was a blocked job. It is guaranteed + // that it will be the next job run by the worker threads once this + // lock is released. + if (unblocked == null) { + + if (likeThreadJob != null) { + + // look for any explicit jobs we may be blocking + unblocked = ((InternalJob) likeThreadJob).previous(); + + if (unblocked == null) { + + // look for any implicit (or yielding) jobs we may be blocking. + unblocked = findBlockedJob(likeThreadJob, waitingThreadJobs.iterator()); + } + + } else { + + // look for any implicit (or yielding) jobs we may be blocking. + unblocked = findBlockedJob(job, waitingThreadJobs.iterator()); + } + } + + // optimization: do nothing if we don't unblock any job + if (unblocked == null) + return null; + + // "release" our rule by exiting RUNNING state + changeState(job, InternalJob.YIELDING); + if (DEBUG_YIELDING) + JobManager.debug(job + " will yieldRule to " + unblocked); //$NON-NLS-1$ + + if (likeThreadJob != null && likeThreadJob != job) { + // if there is a corresponding thread job, it needs yield as well + changeState(likeThreadJob, InternalJob.YIELDING); + if (DEBUG_YIELDING) + JobManager.debug(job + " will yieldRule to " + unblocked); //$NON-NLS-1$ + } + + if (likeThreadJob != null) { + // only null-out threads out for non-ThreadJobs + job.setThread(null); + if (likeThreadJob.getRule() != null) { + getLockManager().removeLockThread(currentThread, likeThreadJob.getRule()); + } + } + + if ((job.getRule() != null) && !(job instanceof ThreadJob)) + getLockManager().removeLockThread(currentThread, job.getRule()); + } + } + // To prevent this job from immediately re-grabbing the scheduling rule wait until + // the unblocked job changes state. This unblocked job is guaranteed to be the + // next job of the set of similar conflicting rules to attempt to run. + if (DEBUG_YIELDING_DETAILED) + JobManager.debug(job + " is waiting for " + unblocked + " to transition from WAITING state"); //$NON-NLS-1$ //$NON-NLS-2$ + + waitForUnblocked(unblocked); + + // restart this job, unless we've been restarted already + // This is the same as ThreadJob begin, except that cancelation CAN NOT be supported + // throwing the OperationCanceledException will return execution to the caller. + IProgressMonitor mon = monitorFor(monitor); + ProgressMonitorWrapper nonCanceling = new ProgressMonitorWrapper(mon) { + @Override + public boolean isCanceled() { + // pass-through request + getWrappedProgressMonitor().isCanceled(); + // ignore result + return false; + } + }; + + if (DEBUG_YIELDING) + JobManager.debug(job + " waiting to resume"); //$NON-NLS-1$ + + // this yielding job becomes an implicit job, unless it is one already + if (likeThreadJob == null) { + // Create a Threadjob proxy. This is strictly an internal job, but its not + // preventing from "leaking" out to clients in the form of listener + // notifications, and via IJobManager API usage like find(). + // Set a flag to differentiate it from regular ThreadJobs. + ThreadJob threadJob = new ThreadJob(job.getRule()) { + @Override + boolean isResumingAfterYield() { + return true; + } + }; + threadJob.setRealJob((Job) job); + ThreadJob.joinRun(threadJob, nonCanceling); + // the following state changes are atomic + synchronized (lock) { + // Must end the temporary threadJob to remove from running list + changeState(threadJob, Job.NONE); + changeState(job, Job.RUNNING); + job.setThread(currentThread); + } + } else { + ThreadJob.joinRun(likeThreadJob, nonCanceling); + synchronized (lock) { + changeState(job, Job.RUNNING); + job.setThread(currentThread); + } + } + if (DEBUG_YIELDING) { + // extra assert: make sure no other conflicting jobs are running now + synchronized (lock) { + for (InternalJob other : running) { + if (other == job) + continue; + Assert.isTrue(!other.isConflicting(job), other + " conflicts and ran simultaneously with " + job); //$NON-NLS-1$ + } + } + JobManager.debug(job + " resumed"); //$NON-NLS-1$ + } + if (unblocked instanceof ThreadJob && ((ThreadJob) unblocked).isResumingAfterYield()) { + // if the unblocked job is a proxy for a yielding job to start, return + // the original job. No need to expose the proxy ThreadJob. + return ((ThreadJob) unblocked).realJob; + } + return (Job) unblocked; + } + + private void waitForUnblocked(InternalJob theJob) { + // wait until theJob leaves WAITING state + boolean interrupted = false; + synchronized (theJob.jobStateLock) { + if (theJob instanceof ThreadJob) { + // We can't acquire the implicitJob lock while holding jobStateLock, + // so use isWaiting instead. + while (((ThreadJob) theJob).isWaiting) { + try { + theJob.jobStateLock.wait(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } else { + while (theJob.internalGetState() == Job.WAITING) { + try { + theJob.jobStateLock.wait(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } + } + if (interrupted) + Thread.currentThread().interrupt(); + } + + /** + * Invokes {@link Job#shouldRun()} while guarding against unexpected failures. + */ + private boolean shouldRun(Job job) { + Throwable t; + try { + return job.shouldRun(); + } catch (Exception e) { + t = e; + } catch (LinkageError e) { + t = e; + } catch (AssertionError e) { + t = e; + } + RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "Error invoking shouldRun() method on: " + job, t)); //$NON-NLS-1$ + //if the should is unexpectedly failing it is safer not to run it + return false; + } + + /** + * Returns the next job to be run, or null if no jobs are waiting to run. + * The worker must call endJob when the job is finished running. + */ + protected Job startJob(Worker worker) { + Job job = null; + while (true) { + job = nextJob(); + if (job == null) + return null; + //must perform this outside sync block because it is third party code + boolean shouldRun = shouldRun(job); + //check for listener veto + if (shouldRun) + jobListeners.aboutToRun(job); + //listeners may have canceled or put the job to sleep + boolean endJob = false; + synchronized (lock) { + JobGroup jobGroup = job.getJobGroup(); + if (jobGroup != null && jobGroup.getState() == JobGroup.CANCELING) + shouldRun = false; + InternalJob internal = job; + synchronized (internal.jobStateLock) { + if (internal.internalGetState() == InternalJob.ABOUT_TO_RUN) { + if (shouldRun && !internal.isAboutToRunCanceled()) { + internal.setProgressMonitor(createMonitor(job)); + //change from ABOUT_TO_RUN to RUNNING + internal.setThread(worker); + internal.internalSetState(Job.RUNNING); + internal.jobStateLock.notifyAll(); + break; + } + internal.setAboutToRunCanceled(false); + endJob = true; + //fall through and end the job below + } + } + } + if (endJob) { + //job has been vetoed or canceled, so mark it as done + endJob(job, Status.CANCEL_STATUS, true); + continue; + } + } + jobListeners.running(job); + return job; + + } + + @Override + public final void suspend() { + synchronized (lock) { + suspended = true; + } + } + + @Deprecated + @Override + public final void suspend(ISchedulingRule rule, IProgressMonitor monitor) { + Assert.isNotNull(rule); + implicitJobs.suspend(rule, monitorFor(monitor)); + } + + @Override + public void transferRule(ISchedulingRule rule, Thread destinationThread) { + implicitJobs.transfer(rule, destinationThread); + } + + /** + * Validates that the given scheduling rule obeys the constraints of + * scheduling rules as described in the ISchedulingRule + * javadoc specification. + */ + private void validateRule(ISchedulingRule rule) { + //null rule always valid + if (rule == null) + return; + if (rule instanceof MultiRule) { + ISchedulingRule[] children = ((MultiRule) rule).getChildren(); + for (ISchedulingRule element : children) { + Assert.isLegal(element != rule); + validateRule(element); + } + } + //contains method must be reflexive + Assert.isLegal(rule.contains(rule)); + //contains method must return false when given an unknown rule + Assert.isLegal(!rule.contains(nullRule)); + //isConflicting method must be reflexive + Assert.isLegal(rule.isConflicting(rule)); + //isConflicting method must return false when given an unknown rule + Assert.isLegal(!rule.isConflicting(nullRule)); + } + + protected void wakeUp(InternalJob job, long delay) { + Assert.isLegal(delay >= 0, "Scheduling delay is negative"); //$NON-NLS-1$ + synchronized (lock) { + //cannot wake up if it is not sleeping + if (job.getState() != Job.SLEEPING) + return; + doSchedule(job, delay); + } + //call the pool outside sync block to avoid deadlock + pool.jobQueued(); + + //only notify of wake up if immediate + if (delay == 0) + jobListeners.awake((Job) job); + } + + @Override + public void wakeUp(Object family) { + //don't synchronize because wakeUp calls listeners + for (InternalJob internalJob : select(family)) { + wakeUp(internalJob, 0L); + } + } + + void endMonitoring(ThreadJob threadJob) { + synchronized (monitorStack) { + for (int i = monitorStack.size() - 1; i >= 0; i--) { + if (monitorStack.get(i)[0] == threadJob) { + monitorStack.remove(i); + monitorStack.notifyAll(); + break; + } + } + } + } + + void beginMonitoring(ThreadJob threadJob, IProgressMonitor monitor) { + synchronized (monitorStack) { + monitorStack.add(new Object[] {threadJob, monitor}); + monitorStack.notifyAll(); + } + } + + /** + * Listens for the job completion events and checks for the job group cancellation, + * computes and logs the group result. + */ + private class JobGroupUpdater extends JobChangeAdapter { + Object jobManagerLock; + + public JobGroupUpdater(Object jobManagerLock) { + this.jobManagerLock = jobManagerLock; + } + + @Override + public void done(IJobChangeEvent event) { + InternalJob job = event.getJob(); + InternalJobGroup jobGroup = job.getJobGroup(); + if (jobGroup == null) + return; + IStatus jobResult = event.getResult(); + boolean reschedule = ((JobChangeEvent) event).reschedule; + + int jobGroupState; + int activeJobsCount; + int failedJobsCount; + int canceledJobsCount; + int seedJobsRemainingCount; + List jobResults = Collections.emptyList(); + synchronized (jobManagerLock) { + // Collect the required details to check for the group cancellation and completion + // outside the synchronized block. + jobGroupState = jobGroup.getState(); + activeJobsCount = jobGroup.getActiveJobsCount(); + failedJobsCount = jobGroup.getFailedJobsCount(); + canceledJobsCount = jobGroup.getCanceledJobsCount(); + seedJobsRemainingCount = jobGroup.getSeedJobsRemainingCount(); + if (activeJobsCount == 0) + jobResults = jobGroup.getCompletedJobResults(); + } + + // Check for the group completion. + if (!reschedule && jobGroupState != JobGroup.NONE && activeJobsCount == 0 && (seedJobsRemainingCount <= 0 || jobGroupState == JobGroup.CANCELING)) { + // Must perform this outside the sync block to avoid a potential deadlock + MultiStatus jobGroupResult = jobGroup.computeGroupResult(jobResults); + Assert.isLegal(jobGroupResult != null, "The group result should not be null"); //$NON-NLS-1$ + boolean isJobGroupCompleted = false; + synchronized (jobManagerLock) { + // If more jobs were added to the group while were computing the result, the job group + // remains in the ACTIVE state and the computed result is discarded to be recomputed later, + // after the new jobs finish. + if (jobGroup.getState() != JobGroup.NONE && jobGroup.getActiveJobsCount() == 0) { + jobGroup.endJobGroup(jobGroupResult); + isJobGroupCompleted = true; + } + } + + // If the job group is completing, add the job group's status to the event + // and log errors and warnings. + if (isJobGroupCompleted) { + ((JobChangeEvent) event).jobGroupResult = jobGroupResult; + if (jobGroupResult.matches(IStatus.ERROR | IStatus.WARNING)) + RuntimeLog.log(jobGroupResult); + } + + return; + } + + if (jobGroupState != JobGroup.CANCELING && jobGroup.shouldCancel(jobResult, failedJobsCount, canceledJobsCount)) + cancel(jobGroup, true); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java new file mode 100644 index 0000000000..7fb1c53b08 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobMessages.java @@ -0,0 +1,54 @@ +/********************************************************************** + * 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 - Initial API and implementation + **********************************************************************/ +package org.eclipse.core.internal.jobs; + +import java.util.Date; +import org.eclipse.osgi.util.NLS; + +/** + * Job plugin message catalog + */ +public class JobMessages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.core.internal.jobs.messages"; //$NON-NLS-1$ + + // Job Manager and Locks + public static String jobs_blocked0; + public static String jobs_blocked1; + public static String jobs_internalError; + public static String jobs_waitFamSub; + public static String jobs_waitFamSubOne; + public static String jobs_returnNoStatus; + // metadata + public static String meta_pluginProblems; + + + static { + // load message values from bundle file + reloadMessages(); + } + + public static void reloadMessages() { + NLS.initializeMessages(BUNDLE_NAME, JobMessages.class); + } + + /** + * Print a debug message to the console. + * Pre-pend the message with the current date and the name of the current thread. + */ + public static void message(String message) { + StringBuffer buffer = new StringBuffer(); + buffer.append(new Date(System.currentTimeMillis())); + buffer.append(" - ["); //$NON-NLS-1$ + buffer.append(Thread.currentThread().getName()); + buffer.append("] "); //$NON-NLS-1$ + buffer.append(message); + System.out.println(buffer.toString()); + } +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java new file mode 100644 index 0000000000..a4d588ab92 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobOSGiUtils.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * 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.jobs; + +import java.util.Hashtable; +import org.eclipse.core.runtime.jobs.IJobManager; +import org.eclipse.osgi.service.debug.DebugOptions; +import org.eclipse.osgi.service.debug.DebugOptionsListener; +import org.osgi.framework.*; +import org.osgi.service.packageadmin.PackageAdmin; +import org.osgi.util.tracker.ServiceTracker; + +/** + * The class contains a set of helper methods for the runtime Jobs plugin. + * The following utility methods are supplied: + * - provides access to debug options + * - provides some bundle discovery functionality + * + * The closeServices() method should be called before the plugin is stopped. + * + * @since org.eclipse.core.jobs 3.2 + */ +class JobOSGiUtils { + private ServiceRegistration debugRegistration = null; + private ServiceTracker bundleTracker = null; + + private static final JobOSGiUtils singleton = new JobOSGiUtils(); + + /** + * Accessor for the singleton instance + * @return The JobOSGiUtils instance + */ + public static JobOSGiUtils getDefault() { + return singleton; + } + + /** + * Private constructor to block instance creation. + */ + private JobOSGiUtils() { + super(); + } + + @SuppressWarnings("unchecked") + void openServices() { + BundleContext context = JobActivator.getContext(); + if (context == null) { + if (JobManager.DEBUG) + JobMessages.message("JobsOSGiUtils called before plugin started"); //$NON-NLS-1$ + return; + } + + // register debug options listener + Hashtable properties = new Hashtable<>(2); + properties.put(DebugOptions.LISTENER_SYMBOLICNAME, JobManager.PI_JOBS); + debugRegistration = context.registerService(DebugOptionsListener.class, JobManager.getInstance(), properties); + + bundleTracker = new ServiceTracker(context, PackageAdmin.class.getName(), null); + bundleTracker.open(); + } + + void closeServices() { + if (debugRegistration != null) { + debugRegistration.unregister(); + debugRegistration = null; + } + if (bundleTracker != null) { + bundleTracker.close(); + bundleTracker = null; + } + } + + /** + * Returns the bundle id of the bundle that contains the provided object, or + * null if the bundle could not be determined. + */ + public String getBundleId(Object object) { + if (bundleTracker == null) { + if (JobManager.DEBUG) + JobMessages.message("Bundle tracker is not set"); //$NON-NLS-1$ + return null; + } + PackageAdmin packageAdmin = (PackageAdmin) bundleTracker.getService(); + if (object == null) + return null; + if (packageAdmin == null) + return null; + Bundle source = packageAdmin.getBundle(object.getClass()); + if (source != null && source.getSymbolicName() != null) + return source.getSymbolicName(); + return null; + } + + /** + * Calculates whether the job plugin should set worker threads to be daemon + * threads. When workers are daemon threads, the job plugin does not need + * to be explicitly shut down because the VM can exit while workers are still + * alive. + * @return true if all worker threads should be daemon threads, + * and false otherwise. + */ + boolean useDaemonThreads() { + BundleContext context = JobActivator.getContext(); + if (context == null) { + //we are running stand-alone, so consult global system property + String value = System.getProperty(IJobManager.PROP_USE_DAEMON_THREADS); + //default to use daemon threads if property is absent + if (value == null) + return true; + return "true".equalsIgnoreCase(value); //$NON-NLS-1$ + } + //only use daemon threads if the property is defined + final String value = context.getProperty(IJobManager.PROP_USE_DAEMON_THREADS); + //if value is absent, don't use daemon threads to maintain legacy behaviour + if (value == null) + return false; + return "true".equalsIgnoreCase(value); //$NON-NLS-1$ + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java new file mode 100644 index 0000000000..356c643698 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobQueue.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * 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.Iterator; +import org.eclipse.core.runtime.*; + +/** + * A linked list based priority queue. + */ +public final class JobQueue { + /** + * The dummy entry sits between the head and the tail of the queue. + * dummy.previous() is the head, and dummy.next() is the tail. + */ + protected final InternalJob dummy; + + /** + * If true, conflicting jobs will be allowed to overtake others in the + * queue that have lower priority. If false, higher priority jumps can only + * move up the queue by overtaking jobs that they don't conflict with. + */ + private final boolean allowConflictOvertaking; + + private final boolean allowPriorityOvertaking; + + /** + * Create a new job queue. + */ + public JobQueue(boolean allowConflictOvertaking) { + this(allowConflictOvertaking, true); + } + + /** + * Create a new job queue. + */ + public JobQueue(boolean allowConflictOvertaking, boolean allowPriorityOvertaking) { + this.allowPriorityOvertaking = allowPriorityOvertaking; + //compareTo on dummy is never called + dummy = new InternalJob("Queue-Head") {//$NON-NLS-1$ + @Override + public IStatus run(IProgressMonitor m) { + return Status.OK_STATUS; + } + }; + dummy.setNext(dummy); + dummy.setPrevious(dummy); + this.allowConflictOvertaking = allowConflictOvertaking; + } + + /** + * remove all elements + */ + public void clear() { + dummy.setNext(dummy); + dummy.setPrevious(dummy); + } + + /** + * Return and remove the element with highest priority, or null if empty. + */ + public InternalJob dequeue() { + InternalJob toRemove = dummy.previous(); + if (toRemove == dummy) + return null; + return toRemove.remove(); + } + + /** + * Adds an item to the queue + */ + public void enqueue(InternalJob newEntry) { + //assert new entry is does not already belong to some other data structure + Assert.isTrue(newEntry.next() == null); + Assert.isTrue(newEntry.previous() == null); + InternalJob tail = dummy.next(); + //overtake lower priority jobs. Only overtake conflicting jobs if allowed to + while (canOvertake(newEntry, tail)) + tail = tail.next(); + //new entry is smaller than tail + final InternalJob tailPrevious = tail.previous(); + newEntry.setNext(tail); + newEntry.setPrevious(tailPrevious); + tailPrevious.setNext(newEntry); + tail.setPrevious(newEntry); + } + + /** + * Returns whether the new entry to overtake the existing queue entry. + * @param newEntry The entry to be added to the queue + * @param queueEntry The existing queue entry + */ + private boolean canOvertake(InternalJob newEntry, InternalJob queueEntry) { + //can never go past the end of the queue + if (queueEntry == dummy) + return false; + //if the new entry was already in the wait queue, ensure it is re-inserted in correct position (bug 211799) + if (newEntry.getWaitQueueStamp() > 0 && newEntry.getWaitQueueStamp() < queueEntry.getWaitQueueStamp()) + return true; + //if the new entry has lower priority, there is no need to overtake the existing entry + if (allowPriorityOvertaking && queueEntry.compareTo(newEntry) >= 0) + return false; + //the new entry has higher priority, but only overtake the existing entry if the queue allows it + return allowConflictOvertaking || !newEntry.isConflicting(queueEntry); + } + + /** + * Removes the given element from the queue. + */ + public void remove(InternalJob toRemove) { + toRemove.remove(); + //previous of toRemove might now bubble up + } + + /** + * The given object has changed priority. Reshuffle the heap until it is + * valid. + */ + public void resort(InternalJob entry) { + remove(entry); + enqueue(entry); + } + + /** + * Returns true if the queue is empty, and false otherwise. + */ + public boolean isEmpty() { + return dummy.next() == dummy; + } + + /** + * Return greatest element without removing it, or null if empty + */ + public InternalJob peek() { + return dummy.previous() == dummy ? null : dummy.previous(); + } + + public Iterator iterator() { + return new Iterator() { + InternalJob pointer = dummy; + + @Override + public boolean hasNext() { + if (pointer.previous() == dummy) + pointer = null; + else + pointer = pointer.previous(); + return pointer != null; + } + + @Override + public Object next() { + return pointer; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java new file mode 100644 index 0000000000..af4f97e62d --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobStatus.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * 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.internal.jobs; + +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.IJobStatus; +import org.eclipse.core.runtime.jobs.Job; + +/** + * Standard implementation of the IJobStatus interface. + */ +public class JobStatus extends Status implements IJobStatus { + private Job job; + + /** + * Creates a new job status with no interesting error code or exception. + * @param severity + * @param job + * @param message + */ + public JobStatus(int severity, Job job, String message) { + super(severity, JobManager.PI_JOBS, 1, message, null); + this.job = job; + } + + @Override + public Job getJob() { + return job; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java new file mode 100644 index 0000000000..f2054a21ae --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/LockManager.java @@ -0,0 +1,334 @@ +/******************************************************************************* + * 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 java.util.HashMap; +import java.util.Stack; +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.LockListener; + +/** + * Stores the only reference to the graph that contains all the known + * relationships between locks, rules, and the threads that own them. + * Synchronizes all access to the graph on the only instance that exists in this class. + * + * Also stores the state of suspended locks so that they can be re-acquired with + * the proper lock depth. + */ +public class LockManager { + /** + * This class captures the state of suspended locks. + * Locks are suspended if deadlock is detected. + */ + private static class LockState { + private int depth; + private OrderedLock lock; + + /** + * Suspends ownership of the given lock, and returns the saved state. + */ + protected static LockState suspend(OrderedLock lock) { + LockState state = new LockState(); + state.lock = lock; + state.depth = lock.forceRelease(); + return state; + } + + /** + * Re-acquires a suspended lock and reverts to the correct lock depth. + */ + public void resume() { + //spin until the lock is successfully acquired + //NOTE: spinning here allows the UI thread to service pending syncExecs + //if the UI thread is waiting to acquire a lock. + while (true) { + try { + if (lock.acquire(Long.MAX_VALUE)) + break; + } catch (InterruptedException e) { + //ignore and loop + } + } + lock.setDepth(depth); + } + } + + //the lock listener for this lock manager + protected LockListener lockListener; + /* + * The internal data structure that stores all the relationships + * between the locks (or rules) and the threads that own them. + */ + private DeadlockDetector locks = new DeadlockDetector(); + /* + * Stores thread - stack pairs where every entry in the stack is an array + * of locks that were suspended while the thread was acquiring more locks + * (a stack is needed because when a thread tries to re-acquire suspended locks, + * it can cause deadlock, and some locks it owns can be suspended again) + */ + private HashMap> suspendedLocks = new HashMap<>(); + + public LockManager() { + super(); + } + + public void aboutToRelease() { + if (lockListener == null) + return; + try { + lockListener.aboutToRelease(); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + } + + public boolean canBlock() { + if (lockListener == null) + return true; + try { + return lockListener.canBlock(); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + return false; + } + + public boolean aboutToWait(Thread lockOwner) { + if (lockListener == null) + return false; + try { + return lockListener.aboutToWait(lockOwner); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + return false; + } + + /** + * This thread has just acquired a lock. Update graph. + */ + void addLockThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockAcquired(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just been refused a lock. Update graph and check for deadlock. + */ + void addLockWaitThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + Deadlock found = null; + synchronized (tempLocks) { + try { + found = tempLocks.lockWaitStart(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + if (found == null) + return; + // if deadlock was detected, the found variable will contain all the information about it, + // including which locks to suspend for which thread to resolve the deadlock. + ISchedulingRule[] toSuspend = found.getLocks(); + LockState[] suspended = new LockState[toSuspend.length]; + for (int i = 0; i < toSuspend.length; i++) + suspended[i] = LockState.suspend((OrderedLock) toSuspend[i]); + synchronized (suspendedLocks) { + Stack prevLocks = suspendedLocks.get(found.getCandidate()); + if (prevLocks == null) + prevLocks = new Stack<>(); + prevLocks.push(suspended); + suspendedLocks.put(found.getCandidate(), prevLocks); + } + } catch (Exception e) { + handleInternalError(e); + } + } + + private Exception createDebugException(DeadlockDetector tempLocks, Exception rootException) { + String debugString = null; + try { + debugString = tempLocks.toDebugString(); + } catch (Exception e) { + //ignore failure to create the debug string + } + return new Exception(debugString, rootException); + } + + /** + * Handles exceptions that occur while calling third party code from within the + * LockManager. This is essentially an in-lined version of Platform.run(ISafeRunnable) + */ + private static void handleException(Throwable e) { + IStatus status; + if (e instanceof CoreException) { + //logged message should not be translated + status = new MultiStatus(JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "LockManager.handleException", e); //$NON-NLS-1$ + ((MultiStatus) status).merge(((CoreException) e).getStatus()); + } else { + status = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "LockManager.handleException", e); //$NON-NLS-1$ + } + RuntimeLog.log(status); + } + + /** + * There was an internal error in the deadlock detection code. Shut the entire + * thing down to prevent further errors. Recovery is too complex as it + * requires freezing all threads and inferring the present lock state. + */ + private void handleInternalError(Throwable t) { + try { + handleException(t); + } catch (Exception e) { + //ignore failure to log + } + //discard the deadlock detector for good + locks = null; + } + + /** + * Returns true IFF the underlying graph is empty. + * For debugging purposes only. + */ + public boolean isEmpty() { + return locks.isEmpty(); + } + + /** + * Returns true IFF this thread either owns, or is waiting for, any locks or rules. + */ + public boolean isLockOwner() { + //all job threads have to be treated as lock owners because UI thread + //may try to join a job + Thread current = Thread.currentThread(); + if (current instanceof Worker) + return true; + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return false; + synchronized (tempLocks) { + return tempLocks.contains(Thread.currentThread()); + } + } + + /** + * Creates and returns a new lock. + */ + public synchronized OrderedLock newLock() { + return new OrderedLock(this); + } + + /** + * Releases all the acquires that were called on the given rule. Needs to be called only once. + */ + void removeLockCompletely(Thread thread, ISchedulingRule rule) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockReleasedCompletely(thread, rule); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just released a lock. Update graph. + */ + void removeLockThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockReleased(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just stopped waiting for a lock. Update graph. + * If the thread has already been granted the lock (or wasn't waiting + * for the lock) then the graph remains unchanged. + */ + void removeLockWaitThread(Thread thread, ISchedulingRule lock) { + DeadlockDetector tempLocks = locks; + if (tempLocks == null) + return; + try { + synchronized (tempLocks) { + try { + tempLocks.lockWaitStop(thread, lock); + } catch (Exception e) { + throw createDebugException(tempLocks, e); + } + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * Resumes all the locks that were suspended while this thread was waiting to acquire another lock. + */ + void resumeSuspendedLocks(Thread owner) { + LockState[] toResume; + synchronized (suspendedLocks) { + Stack prevLocks = suspendedLocks.get(owner); + if (prevLocks == null) + return; + toResume = (LockState[]) prevLocks.pop(); + if (prevLocks.empty()) + suspendedLocks.remove(owner); + } + for (LockState element : toResume) + element.resume(); + } + + public void setLockListener(LockListener listener) { + this.lockListener = listener; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java new file mode 100644 index 0000000000..1983c5c962 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java @@ -0,0 +1,311 @@ +/******************************************************************************* + * 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.internal.jobs; + +import java.util.*; + +/** + * A specialized map implementation that is optimized for a small set of object + * keys. + * + * Implemented as a single array that alternates keys and values. + * + * Note: This class is copied from org.eclipse.core.resources + */ +public class ObjectMap implements Map { + // 8 attribute keys, 8 attribute values + protected static final int DEFAULT_SIZE = 16; + protected static final int GROW_SIZE = 10; + protected int count = 0; + protected Object[] elements = null; + + /** + * Creates a new object map. + * + * @param initialCapacity + * The initial number of elements that will fit in the map. + */ + public ObjectMap(int initialCapacity) { + if (initialCapacity > 0) + elements = new Object[Math.max(initialCapacity * 2, 0)]; + } + + /** + * Creates a new object map of the same size as the given map and populate + * it with the key/attribute pairs found in the map. + * + * @param map + * The entries in the given map will be added to the new map. + */ + public ObjectMap(Map map) { + this(map.size()); + putAll(map); + } + + /** + * @see Map#clear() + */ + @Override + public void clear() { + elements = null; + count = 0; + } + + /** + * @see java.lang.Object#clone() + */ + @Override + public Object clone() { + return new ObjectMap(this); + } + + /** + * @see Map#containsKey(java.lang.Object) + */ + @Override + public boolean containsKey(Object key) { + if (elements == null || count == 0) + return false; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return true; + return false; + } + + /** + * @see Map#containsValue(java.lang.Object) + */ + @Override + public boolean containsValue(Object value) { + if (elements == null || 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; + } + + /** + * @see Map#entrySet() + * + * Note: 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 count == 0 ? Collections.EMPTY_SET : toHashMap().entrySet(); + } + + /** + * @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; + //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; + } + + /** + * @see Map#get(java.lang.Object) + */ + @Override + public Object get(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] != null && elements[i].equals(key)) + return elements[i + 1]; + return null; + } + + /** + * The capacity of the map has been exceeded, grow the array by GROW_SIZE to + * accommodate more entries. + */ + protected void grow() { + Object[] expanded = new Object[elements.length + GROW_SIZE]; + System.arraycopy(elements, 0, expanded, 0, elements.length); + elements = expanded; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + int hash = 0; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + hash += elements[i].hashCode(); + } + } + return hash; + } + + /** + * @see Map#isEmpty() + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * @see Map#keySet() + * + * Note: 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()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.add(elements[i]); + } + } + return result; + } + + /** + * @see Map#put(java.lang.Object, java.lang.Object) + */ + @Override + public Object put(Object key, Object value) { + if (key == null) + throw new NullPointerException(); + if (value == null) + return remove(key); + + // handle the case where we don't have any attributes yet + if (elements == null) + elements = new Object[DEFAULT_SIZE]; + if (count == 0) { + elements[0] = key; + elements[1] = value; + count++; + return null; + } + + int emptyIndex = -1; + // replace existing value if it exists + for (int i = 0; i < elements.length; i += 2) { + if (elements[i] != null) { + if (elements[i].equals(key)) { + Object oldValue = elements[i + 1]; + elements[i + 1] = value; + return oldValue; + } + } else if (emptyIndex == -1) { + // keep track of the first empty index + emptyIndex = i; + } + } + // this will put the emptyIndex greater than the size but + // that's ok because we will grow first. + if (emptyIndex == -1) + emptyIndex = count * 2; + + // otherwise add it to the list of elements. + // grow if necessary + if (elements.length <= (count * 2)) + grow(); + elements[emptyIndex] = key; + elements[emptyIndex + 1] = value; + count++; + return null; + } + + /** + * @see Map#putAll(java.util.Map) + */ + @Override + public void putAll(Map map) { + for (Iterator i = map.keySet().iterator(); i.hasNext();) { + Object key = i.next(); + Object value = map.get(key); + put(key, value); + } + } + + /** + * @see Map#remove(java.lang.Object) + */ + @Override + public Object remove(Object key) { + if (elements == null || count == 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null && elements[i].equals(key)) { + elements[i] = null; + Object result = elements[i + 1]; + elements[i + 1] = null; + count--; + return result; + } + } + return null; + } + + /** + * @see Map#size() + */ + @Override + public int size() { + return count; + } + + /** + * Creates a new hash map with the same contents as this map. + */ + private HashMap toHashMap() { + HashMap result = new HashMap<>(size()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.put(elements[i], elements[i + 1]); + } + } + return result; + } + + /** + * @see Map#values() + * + * Note: 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()); + for (int i = 1; i < elements.length; i = i + 2) { + if (elements[i] != null) { + result.add(elements[i]); + } + } + return result; + } +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java new file mode 100644 index 0000000000..4744e198b6 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/OrderedLock.java @@ -0,0 +1,310 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - Bug 311863 Ordered Lock lost after interrupt + *******************************************************************************/ +package org.eclipse.core.internal.jobs; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.jobs.ILock; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * A lock used to control write access to an exclusive resource. + * + * The lock avoids circular waiting deadlocks by detecting the deadlocks + * and resolving them through the suspension of all locks owned by one + * of the threads involved in the deadlock. This makes it impossible for n such + * locks to deadlock while waiting for each other. The down side is that this means + * that during an interval when a process owns a lock, it can be forced + * to give the lock up and wait until all locks it requires become + * available. This removes the feature of exclusive access to the + * resource in contention for the duration between acquire() and + * release() calls. + * + * The lock implementation prevents starvation by granting the + * lock in the same order in which acquire() requests arrive. In + * this scheme, starvation is only possible if a thread retains + * a lock indefinitely. + */ +public class OrderedLock implements ILock, ISchedulingRule { + + private static final boolean DEBUG = false; + /** + * Locks are sequentially ordered for debugging purposes. + */ + private static int nextLockNumber = 0; + /** + * The thread of the operation that currently owns the lock. + */ + private volatile Thread currentOperationThread; + /** + * Records the number of successive acquires in the same + * thread. The lock is released only when the depth + * reaches zero. + */ + private int depth; + /** + * The manager that implements the deadlock detection and resolution protocol. + */ + private final LockManager manager; + private final int number; + + /** + * Queue of semaphores for threads currently waiting + * on the lock. This queue is not thread-safe, so access + * to this queue must be synchronized on the lock instance. + */ + private final Queue operations = new Queue(); + + /** + * Creates a new workspace lock. + */ + OrderedLock(LockManager manager) { + this.manager = manager; + this.number = nextLockNumber++; + } + + @Override + public void acquire() { + //spin until the lock is successfully acquired + //NOTE: spinning here allows the UI thread to service pending syncExecs + //if the UI thread is waiting to acquire a lock. + boolean interrupted = false; + while (true) { + try { + if (acquire(Long.MAX_VALUE)) + break; + } catch (InterruptedException e) { + interrupted = true; + } + } + //preserve thread interrupt state + if (interrupted) + Thread.currentThread().interrupt(); + } + + @Override + public boolean acquire(long delay) throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + + boolean success = false; + if (delay <= 0) + return attempt(); + Semaphore semaphore = createSemaphore(); + if (semaphore == null) + return true; + if (DEBUG) + System.out.println("[" + Thread.currentThread() + "] Operation waiting to be executed... " + this); //$NON-NLS-1$ //$NON-NLS-2$ + success = doAcquire(semaphore, delay); + manager.resumeSuspendedLocks(Thread.currentThread()); + if (DEBUG) + System.out.println("[" + Thread.currentThread() + //$NON-NLS-1$ + (success ? "] Operation started... " : "] Operation timed out... ") + this); //$NON-NLS-1$ //$NON-NLS-2$ //} + if (!success && Thread.interrupted()) + throw new InterruptedException(); + return success; + } + + /** + * Attempts to acquire the lock. Returns false if the lock is not available and + * true if the lock has been successfully acquired. + */ + private synchronized boolean attempt() { + //return true if we already own the lock + //also, if nobody is waiting, grant the lock immediately + if ((currentOperationThread == Thread.currentThread()) || (currentOperationThread == null && operations.isEmpty())) { + depth++; + setCurrentOperationThread(Thread.currentThread()); + return true; + } + return false; + } + + @Override + public boolean contains(ISchedulingRule rule) { + return rule == this; + } + + /** + * Returns null if acquired and a Semaphore object otherwise. If a + * waiting semaphore already exists for this thread, it will be returned, + * otherwise a new semaphore will be created, enqueued, and returned. + */ + private synchronized Semaphore createSemaphore() { + return attempt() ? null : enqueue(new Semaphore(Thread.currentThread())); + } + + /** + * Attempts to acquire this lock. Callers will block until this lock comes available to + * them, or until the specified delay has elapsed. + */ + private boolean doAcquire(Semaphore semaphore, long delay) { + boolean success = false; + //notify hook to service pending syncExecs before falling asleep + if (manager.aboutToWait(this.currentOperationThread)) { + //hook granted immediate access + //remove semaphore for the lock request from the queue + //do not log in graph because this thread did not really get the lock + removeFromQueue(semaphore); + depth++; + manager.addLockThread(currentOperationThread, this); + return true; + } + //Make sure the semaphore is in the queue before we start waiting + //It might have been removed from the queue while servicing syncExecs + //This is will return our existing semaphore if it is still in the queue + semaphore = createSemaphore(); + if (semaphore == null) + return true; + final Thread currentThread = Thread.currentThread(); + manager.addLockWaitThread(currentThread, this); + try { + success = semaphore.acquire(delay); + } catch (InterruptedException e) { + if (DEBUG) + System.out.println("[" + currentThread + "] Operation interrupted while waiting... :-|"); //$NON-NLS-1$ //$NON-NLS-2$ + //remember the interrupt to throw it later + currentThread.interrupt(); + } + return updateOperationQueue(semaphore, success); + } + + /** + * Releases this lock from the thread that used to own it. + * Grants this lock to the next thread in the queue. + */ + private synchronized void doRelease() { + //notify hook + manager.aboutToRelease(); + depth = 0; + Semaphore next = (Semaphore) operations.peek(); + setCurrentOperationThread(null); + if (next != null) + next.release(); + } + + /** + * If there is another semaphore with the same runnable in the + * queue, the other is returned and the new one is not added. + */ + private synchronized Semaphore enqueue(Semaphore newSemaphore) { + Semaphore semaphore = (Semaphore) operations.get(newSemaphore); + if (semaphore == null) { + operations.enqueue(newSemaphore); + return newSemaphore; + } + return semaphore; + } + + /** + * Suspend this lock by granting the lock to the next lock in the queue. + * Return the depth of the suspended lock. + */ + protected int forceRelease() { + int oldDepth = depth; + doRelease(); + return oldDepth; + } + + @Override + public int getDepth() { + return depth; + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + return rule == this; + } + + @Override + public void release() { + if (depth == 0) + return; + //only release the lock when the depth reaches zero + Assert.isTrue(depth >= 0, "Lock released too many times"); //$NON-NLS-1$ + if (--depth == 0) + doRelease(); + else + manager.removeLockThread(currentOperationThread, this); + } + + /** + * Removes a semaphore from the queue of waiting operations. + * + * @param semaphore The semaphore to remove + */ + private synchronized void removeFromQueue(Semaphore semaphore) { + operations.remove(semaphore); + } + + /** + * If newThread is null, release this lock from its previous owner. + * If newThread is not null, grant this lock to newThread. + */ + private void setCurrentOperationThread(Thread newThread) { + if ((currentOperationThread != null) && (newThread == null)) + manager.removeLockThread(currentOperationThread, this); + this.currentOperationThread = newThread; + if (currentOperationThread != null) + manager.addLockThread(currentOperationThread, this); + } + + /** + * Forces the lock to be at the given depth. + * Used when re-acquiring a suspended lock. + */ + protected void setDepth(int newDepth) { + for (int i = depth; i < newDepth; i++) { + manager.addLockThread(currentOperationThread, this); + } + this.depth = newDepth; + } + + /** + * For debugging purposes only. + */ + @Override + public String toString() { + return "OrderedLock (" + number + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * This lock has just been granted to a new thread (the thread waited for it). + * Remove the request from the queue and update both the graph and the lock. + */ + private synchronized void updateCurrentOperation() { + operations.dequeue(); + setCurrentOperationThread(Thread.currentThread()); + } + + /** + * We have finished waiting on the given semaphore. Update the operation queue according + * to whether we succeeded in obtaining the lock. + * + * @param semaphore The semaphore that we waited on + * @param acquired true if we successfully acquired the semaphore, and false otherwise + * @return whether the lock was successfully obtained + */ + private synchronized boolean updateOperationQueue(Semaphore semaphore, boolean acquired) { + // Bug 311863 - Semaphore may have been released concurrently, so check again before discarding it + if (!acquired) + acquired = semaphore.attempt(); + if (acquired) { + depth++; + updateCurrentOperation(); + } else { + removeFromQueue(semaphore); + manager.removeLockWaitThread(Thread.currentThread(), this); + } + return acquired; + } + +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java new file mode 100644 index 0000000000..03c1bb94cd --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Queue.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * 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.*; + +/** + * A Queue of objects. + */ +public class Queue { + protected Object[] elements; + protected int head; + protected boolean reuse; + protected int tail; + + public Queue() { + this(20, false); + } + + /** + * The parameter reuse indicates what do you want to happen with + * the object reference when you remove it from the queue. If + * reuse is false the queue no longer holds a reference to the + * object when it is removed. If reuse is true you can use the + * method getNextAvailableObject to get an used object, set its + * new values and add it again to the queue. + */ + public Queue(int size, boolean reuse) { + elements = new Object[size]; + head = tail = 0; + this.reuse = reuse; + } + + /** + * Adds an object to the tail of the queue. + */ + public void enqueue(Object element) { + int newTail = increment(tail); + if (newTail == head) { + grow(); + newTail = tail + 1; + } + elements[tail] = element; + tail = newTail; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to decrement an index in the queue. + */ + public int decrement(int index) { + return (index == 0) ? (elements.length - 1) : index - 1; + } + + public Iterator elements() { + /**/ + if (isEmpty()) + return new ArrayList(0).iterator(); + + /* if head < tail we can use the same array */ + if (head <= tail) + return Arrays.asList(elements).iterator(); + + /* otherwise we need to create a new array */ + Object[] newElements = new Object[size()]; + int end = (elements.length - head); + System.arraycopy(elements, head, newElements, 0, end); + System.arraycopy(elements, 0, newElements, end, tail); + return Arrays.asList(newElements).iterator(); + } + + public Object get(Object o) { + int index = head; + while (index != tail) { + if (elements[index].equals(o)) + return elements[index]; + index = increment(index); + } + return null; + } + + /** + * Removes the given object from the queue. Shifts the underlying array. + */ + public boolean remove(Object o) { + int index = head; + //find the object to remove + while (index != tail) { + if (elements[index].equals(o)) + break; + index = increment(index); + } + //if element wasn't found, return + if (index == tail) + return false; + //store a reference to it (needed for reuse of objects) + Object toRemove = elements[index]; + int nextIndex = -1; + while (index != tail) { + nextIndex = increment(index); + if (nextIndex != tail) + elements[index] = elements[nextIndex]; + + index = nextIndex; + } + //decrement tail + tail = decrement(tail); + + //if objects are reused, transfer the reference that is removed to the end of the queue + //otherwise set the element after the last one to null (to avoid duplicate references) + elements[tail] = reuse ? toRemove : null; + return true; + } + + protected void grow() { + int newSize = (int) (elements.length * 1.5); + Object[] newElements = new Object[newSize]; + if (tail >= head) + System.arraycopy(elements, head, newElements, head, size()); + else { + int newHead = newSize - (elements.length - head); + System.arraycopy(elements, 0, newElements, 0, tail + 1); + System.arraycopy(elements, head, newElements, newHead, (newSize - newHead)); + head = newHead; + } + elements = newElements; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to increment an index in the queue. + */ + public int increment(int index) { + return (index == (elements.length - 1)) ? 0 : index + 1; + } + + public boolean isEmpty() { + return tail == head; + } + + public Object peek() { + return elements[head]; + } + + /** + * Removes an returns the item at the head of the queue. + */ + public Object dequeue() { + if (isEmpty()) + return null; + Object result = peek(); + if (!reuse) + elements[head] = null; + head = increment(head); + return result; + } + + public int size() { + return tail > head ? (tail - head) : ((elements.length - head) + tail); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("["); //$NON-NLS-1$ + if (!isEmpty()) { + Iterator it = elements(); + while (true) { + sb.append(it.next()); + if (it.hasNext()) + sb.append(", "); //$NON-NLS-1$ + else + break; + } + } + sb.append("]"); //$NON-NLS-1$ + return sb.toString(); + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java new file mode 100644 index 0000000000..a169b01278 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Semaphore.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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 + * James Blackburn (Broadcom Corp.) - Bug 311863 Ordered Lock lost after interrupt + *******************************************************************************/ +package org.eclipse.core.internal.jobs; + +public class Semaphore { + protected long notifications; + protected Runnable runnable; + + public Semaphore(Runnable runnable) { + this.runnable = runnable; + notifications = 0; + } + + /** + * Attempts to acquire this semaphore. Returns true if it was successfully acquired, + * and false otherwise. + */ + public synchronized boolean acquire(long delay) throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + long start = System.currentTimeMillis(); + long timeLeft = delay; + while (true) { + if (notifications > 0) { + notifications--; + return true; + } + if (timeLeft <= 0) + return false; + wait(timeLeft); + timeLeft = start + delay - System.currentTimeMillis(); + } + } + + /** + * Attempt to acquire the semaphore without waiting. + * Returns true if successfully acquired, false otherwise. + */ + public synchronized boolean attempt() { + if (notifications > 0) { + notifications--; + return true; + } + return false; + } + + @Override + public boolean equals(Object obj) { + return (runnable == ((Semaphore) obj).runnable); + } + + @Override + public int hashCode() { + return runnable == null ? 0 : runnable.hashCode(); + } + + public synchronized void release() { + notifications++; + notifyAll(); + } + + // for debug only + @Override + public String toString() { + return "Semaphore(" + runnable + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java new file mode 100644 index 0000000000..5f87980755 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java @@ -0,0 +1,467 @@ +/******************************************************************************* + * 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.internal.jobs; + +import org.eclipse.core.internal.runtime.RuntimeLog; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.*; + +/** + * Captures the implicit job state for a given thread. + */ +class ThreadJob extends Job { + + /** + * Set to true if this thread job is running in a thread that did + * not own a rule already. This means it needs to acquire the + * rule during beginRule, and must release the rule during endRule. + */ + protected boolean acquireRule = false; + + /** + * Indicates that this thread job did report to the progress manager + * that it will be blocked, and therefore when it begins it must + * be reported to the job manager when it is no longer blocked. + */ + boolean isBlocked = false; + + /** + * True if this ThreadJob has begun execution + * @GuardedBy("this") + */ + protected boolean isRunning = false; + + /** + * Used for diagnosing mismatched begin/end pairs. This field + * is only used when in debug mode, to capture the stack trace + * of the last call to beginRule. + */ + private RuntimeException lastPush = null; + /** + * The actual job that is running in the thread that this + * ThreadJob represents. This will be null if this thread + * job is capturing a rule acquired outside of a job. + * @GuardedBy("JobManager.implicitJobs") + */ + protected Job realJob; + /** + * The stack of rules that have been begun in this thread, but not yet ended. + * @GuardedBy("JobManager.implicitJobs") + */ + private ISchedulingRule[] ruleStack; + /** + * Rule stack pointer. + * INV: 0 <= top <= ruleStack.length + * @GuardedBy("JobManager.implicitJobs") + */ + private int top; + + /** + * Waiting state for thread jobs is independent of the internal state. When + * this variable is true, this ThreadJob is waiting in joinRun() + * @GuardedBy("jobStateLock") + */ + boolean isWaiting; + + ThreadJob(ISchedulingRule rule) { + super("Implicit Job"); //$NON-NLS-1$ + setSystem(true); + // calling setPriority will try to acquire JobManager.lock, breaking + // lock acquisition protocol. Since we are constructing this thread, + // we can call internalSetPriority + ((InternalJob) this).internalSetPriority(Job.INTERACTIVE); + ruleStack = new ISchedulingRule[2]; + top = -1; + ((InternalJob) this).internalSetRule(rule); + } + + boolean isResumingAfterYield() { + return false; + } + + /** + * An endRule was called that did not match the last beginRule in + * the stack. Report and log a detailed informational message. + * @param rule The rule that was popped + * @GuardedBy("JobManager.implicitJobs") + */ + private void illegalPop(ISchedulingRule rule) { + StringBuffer buf = new StringBuffer("Attempted to endRule: "); //$NON-NLS-1$ + buf.append(rule); + if (top >= 0 && top < ruleStack.length) { + buf.append(", does not match most recent begin: "); //$NON-NLS-1$ + buf.append(ruleStack[top]); + } else { + if (top < 0) + buf.append(", but there was no matching beginRule"); //$NON-NLS-1$ + else + buf.append(", but the rule stack was out of bounds: " + top); //$NON-NLS-1$ + } + buf.append(". See log for trace information if rule tracing is enabled."); //$NON-NLS-1$ + String msg = buf.toString(); + if (JobManager.DEBUG || JobManager.DEBUG_BEGIN_END) { + System.out.println(msg); + Throwable t = lastPush == null ? new IllegalArgumentException() : lastPush; + IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, t); + RuntimeLog.log(error); + } + Assert.isLegal(false, msg); + } + + /** + * Client has attempted to begin a rule that is not contained within + * the outer rule. + */ + private void illegalPush(ISchedulingRule pushRule, ISchedulingRule baseRule) { + StringBuffer buf = new StringBuffer("Attempted to beginRule: "); //$NON-NLS-1$ + buf.append(pushRule); + buf.append(", does not match outer scope rule: "); //$NON-NLS-1$ + buf.append(baseRule); + String msg = buf.toString(); + if (JobManager.DEBUG) { + System.out.println(msg); + IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, new IllegalArgumentException()); + RuntimeLog.log(error); + } + Assert.isLegal(false, msg); + + } + + /** + * Returns true if the monitor is canceled, and false otherwise. + * Protects the caller from exception in the monitor implementation. + */ + static private boolean isCanceled(IProgressMonitor monitor) { + try { + return monitor.isCanceled(); + } catch (RuntimeException e) { + //logged message should not be translated + IStatus status = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "ThreadJob.isCanceled", e); //$NON-NLS-1$ + RuntimeLog.log(status); + } + return false; + } + + /** + * Returns true if this thread job was scheduled and actually started running. + * @GuardedBy("this") + */ + synchronized boolean isRunning() { + return isRunning; + } + + /** + * A reentrant method which will run this ThreadJob immediately if there + * are no existing jobs with conflicting rules, or block until the rule can be acquired. If this + * job must block, the LockListener is given a chance to override. + * If override is not granted, then this method will block until the rule is available. If + * LockListener#canBlock returns true, then the monitor + * will not be periodically checked for cancellation. It will only be rechecked if this + * thread is interrupted. If LockListener#canBlock returns false The + * monitor will be checked periodically for cancellation. + * + * When a UI is present, it is recommended that the LockListener + * should not allow the UI thread to block without checking the monitor. This + * ensures that the UI remains responsive. + * + * @see LockListener#aboutToWait(Thread) + * @see LockListener#canBlock() + * @see JobManager#transferRule(ISchedulingRule, Thread) + + * @return this, or the ThreadJob instance that was + * unblocked (due to transferRule) in the case of reentrant invocations of this method. + * + * @param monitor - The IProgressMonitor used to report blocking status and + * cancellation. + * + * @throws OperationCanceledException if this job was canceled before it was started. + */ + static ThreadJob joinRun(ThreadJob threadJob, IProgressMonitor monitor) { + if (isCanceled(monitor)) + throw new OperationCanceledException(); + // check if there is a blocking thread before waiting + InternalJob blockingJob = manager.findBlockingJob(threadJob); + Thread blocker = blockingJob == null ? null : blockingJob.getThread(); + ThreadJob result; + boolean interruptedDuringWaitForRun; + try { + // just return if lock listener decided to grant immediate access + if (manager.getLockManager().aboutToWait(blocker)) + return threadJob; + result = waitForRun(threadJob, monitor, blockingJob, blocker); + } finally { + // We need to check for interruption unconditionally in order to + // ensure we clear the thread's interrupted state. However, we only + // throw an OperationCanceledException outside of the finally block + // because we only want to throw that exception if we're not already + // throwing some other exception here. + interruptedDuringWaitForRun = Thread.interrupted(); + manager.getLockManager().aboutToRelease(); + } + + // During the call to waitForRun, we use the thread's interrupt flag to + // trigger cancellation, so thread interruption at this time should + // trigger an OCE. + if (interruptedDuringWaitForRun) { + throw new OperationCanceledException(); + } + return result; + } + + private static ThreadJob waitForRun(ThreadJob threadJob, IProgressMonitor monitor, InternalJob blockingJob, + Thread blocker) { + // Ask lock manager if it safe to block this thread + final boolean canBlock = manager.getLockManager().canBlock(); + ThreadJob result = threadJob; + boolean interrupted = false; + boolean waiting = false; + try { + waitStart(threadJob, monitor, blockingJob); + manager.implicitJobs.addWaiting(threadJob); + waiting = true; + // If we're allowed to block this thread we won't be checking the monitor. In order + // to respond to cancellation, register this monitor with the internal JobManager + // worker thread. The worker thread will check for cancellation and will interrupt + // this thread when the monitor is canceled. T + if (canBlock) + manager.beginMonitoring(threadJob, monitor); + final Thread currentThread = Thread.currentThread(); + + // Ultimately, this loop will wait until the job "runs" (acquires the rule) + // or is canceled. However, there are many ways for that to occur. + // The exit conditions of this loop are: + // 1) This job no longer conflicts with any other running job. + // 2) The LockManager#aboutToWait allowed this thread to run. + // This usually occurs during reentrant situations. i.e. This is a UI thread, + // and a syncExec is performed from conflicting job/thread. + // 3) A rule is transfered to this thread. This can be invoked programmatically, + // or commonly in JFace via ModalContext (for wizards/etc). + // 4) Monitor is canceled. + while (true) { + // monitor is foreign code so do not hold locks while calling into monitor + if (interrupted || isCanceled(monitor)) + // Condition #4. + throw new OperationCanceledException(); + // Try to run the job. If result is null, this job was allowed to run. + // If the result is successful, atomically release thread from waiting + // status. + blockingJob = manager.runNow(threadJob, true); + if (blockingJob == null) { + // Condition #1. + waiting = false; + return threadJob; + } + blocker = blockingJob == null ? null : blockingJob.getThread(); + // the rule could have been transferred to this thread while we were waiting + if (blocker == currentThread && blockingJob instanceof ThreadJob) { + // now we are just the nested acquire case + result = (ThreadJob) blockingJob; + result.push(threadJob.getRule()); + result.isBlocked = threadJob.isBlocked; + // Condition #3. + return result; + } + // just return if lock listener decided to grant immediate access + if (manager.getLockManager().aboutToWait(blocker)) + // Condition #2. + return threadJob; + + // Notify the lock manager that we're about to block waiting for the scheduling rule + manager.getLockManager().addLockWaitThread(currentThread, threadJob.getRule()); + synchronized (blockingJob.jobStateLock) { + try { + // Wait until we are no longer definitely blocked (not running). + // The actual exit conditions are listed above at the beginning of + // this while loop + int state = blockingJob.getState(); + //ensure we don't wait forever if the blocker is waiting, because it might have yielded to me + if (state == Job.RUNNING && canBlock) + blockingJob.jobStateLock.wait(); + else if (state != Job.NONE) + blockingJob.jobStateLock.wait(250); + } catch (InterruptedException e) { + // This thread may be interrupted via two common scenarios. 1) If + // the UISynchronizer is in use and this thread is a UI thread + // and a syncExec() is performed, this thread will be interrupted + // every 1000ms. 2) If this thread is allowed to be blocked and + // the progress monitor was canceled, the internal JobManager + // worker thread will interrupt this thread so cancellation can + // be carried out. + interrupted = true; + } + } + // Going around the loop again. Ensure we're not marked as waiting for the thread + // as external code is run via the monitor (Bug 262032). + manager.getLockManager().removeLockWaitThread(currentThread, threadJob.getRule()); + } + } finally { + //only update the lock state if we ended up using the thread job that was given to us + waitEnd(threadJob, threadJob == result, monitor); + if (threadJob == result) { + if (waiting) + manager.implicitJobs.removeWaiting(threadJob); + } + if (canBlock) { + // must unregister monitoring this job + manager.endMonitoring(threadJob); + } + } + } + + /** + * Pops a rule. Returns true if it was the last rule for this thread + * job, and false otherwise. + * @GuardedBy("JobManager.implicitJobs") + */ + boolean pop(ISchedulingRule rule) { + if (top < 0 || ruleStack[top] != rule) + illegalPop(rule); + ruleStack[top--] = null; + return top < 0; + } + + /** + * Adds a new scheduling rule to the stack of rules for this thread. Throws + * a runtime exception if the new rule is not compatible with the base + * scheduling rule for this thread. + * @GuardedBy("JobManager.implicitJobs") + */ + void push(final ISchedulingRule rule) { + final ISchedulingRule baseRule = getRule(); + if (++top >= ruleStack.length) { + ISchedulingRule[] newStack = new ISchedulingRule[ruleStack.length * 2]; + System.arraycopy(ruleStack, 0, newStack, 0, ruleStack.length); + ruleStack = newStack; + } + ruleStack[top] = rule; + if (JobManager.DEBUG_BEGIN_END) + lastPush = (RuntimeException) new RuntimeException().fillInStackTrace(); + //check for containment last because we don't want to fail again on endRule + if (baseRule != null && rule != null && !(baseRule.contains(rule) && baseRule.isConflicting(rule))) + illegalPush(rule, baseRule); + } + + /** + * Reset all of this job's fields so it can be reused. Returns false if + * reuse is not possible + * @GuardedBy("JobManager.implicitJobs") + */ + boolean recycle() { + //don't recycle if still running for any reason + if (getState() != Job.NONE) + return false; + //clear and reset all fields + acquireRule = isRunning = isBlocked = false; + realJob = null; + setRule(null); + setThread(null); + if (ruleStack.length != 2) + ruleStack = new ISchedulingRule[2]; + else + ruleStack[0] = ruleStack[1] = null; + top = -1; + return true; + } + + /** (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public IStatus run(IProgressMonitor monitor) { + synchronized (this) { + isRunning = true; + } + return ASYNC_FINISH; + } + + /** + * Records the job that is actually running in this thread, if any + * @param realJob The running job + * @GuardedBy("JobManager.implicitJobs") + */ + void setRealJob(Job realJob) { + this.realJob = realJob; + } + + /** + * Returns true if this job should cause a self-canceling job + * to cancel itself, and false otherwise. + * @GuardedBy("JobManager.implicitJobs") + */ + boolean shouldInterrupt() { + return realJob == null ? true : !realJob.isSystem(); + } + + /* (non-javadoc) + * For debugging purposes only + */ + @Override + public String toString() { + StringBuffer buf = new StringBuffer("ThreadJob"); //$NON-NLS-1$ + buf.append('(').append(realJob).append(',').append(getRuleStack()).append(')'); + return buf.toString(); + } + + String getRuleStack() { + StringBuffer buf = new StringBuffer(); + buf.append('['); + for (int i = 0; i <= top && i < ruleStack.length; i++) { + buf.append(ruleStack[i]); + if (i != top) + buf.append(','); + } + buf.append(']'); + return buf.toString(); + } + + /** + * Reports that this thread was blocked, but is no longer blocked and is able + * to proceed. + * @param monitor The monitor to report unblocking to. + */ + static private void waitEnd(ThreadJob threadJob, boolean updateLockManager, IProgressMonitor monitor) { + if (updateLockManager) { + final LockManager lockManager = manager.getLockManager(); + final Thread currentThread = Thread.currentThread(); + if (threadJob.isRunning()) { + lockManager.addLockThread(currentThread, threadJob.getRule()); + //need to re-acquire any locks that were suspended while this thread was blocked on the rule + lockManager.resumeSuspendedLocks(currentThread); + } else { + //tell lock manager that this thread gave up waiting + lockManager.removeLockWaitThread(currentThread, threadJob.getRule()); + } + } + if (threadJob.isBlocked) { + threadJob.isBlocked = false; + manager.reportUnblocked(monitor); + } + } + + /** + * Indicates the start of a wait on a scheduling rule. Report the + * blockage to the progress manager. + * @param monitor The monitor to report blocking to + * @param blockingJob The job that is blocking this thread, or null + */ + static private void waitStart(ThreadJob threadJob, IProgressMonitor monitor, InternalJob blockingJob) { + threadJob.isBlocked = true; + manager.reportBlocked(monitor, blockingJob); + } + + /** + * ThreadJobs are one-shot jobs, and they must ignore all attempts to schedule them. + */ + @Override + public boolean shouldSchedule() { + return false; + } +} \ No newline at end of file diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java new file mode 100644 index 0000000000..6e13c0b151 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/Worker.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.core.internal.jobs; + +import org.eclipse.core.internal.runtime.RuntimeLog; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.osgi.util.NLS; + +/** + * A worker thread processes jobs supplied to it by the worker pool. When + * the worker pool gives it a null job, the worker dies. + */ +public class Worker extends Thread { + //worker number used for debugging purposes only + private static int nextWorkerNumber = 0; + private volatile InternalJob currentJob; + private final WorkerPool pool; + + public Worker(WorkerPool pool) { + super("Worker-" + nextWorkerNumber++); //$NON-NLS-1$ + this.pool = pool; + //set the context loader to avoid leaking the current context loader + //for the thread that spawns this worker (bug 98376) + setContextClassLoader(pool.defaultContextLoader); + } + + /** + * Returns the currently running job, or null if none. + */ + public Job currentJob() { + return (Job) currentJob; + } + + private IStatus handleException(InternalJob job, Throwable t) { + String message = NLS.bind(JobMessages.jobs_internalError, job.getName()); + return new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, message, t); + } + + @Override + public void run() { + setPriority(Thread.NORM_PRIORITY); + try { + while ((currentJob = pool.startJob(this)) != null) { + IStatus result = Status.OK_STATUS; + IProgressMonitor monitor = currentJob.getProgressMonitor(); + try { + result = currentJob.run(monitor); + } catch (OperationCanceledException e) { + result = Status.CANCEL_STATUS; + } catch (Exception e) { + result = handleException(currentJob, e); + } catch (ThreadDeath e) { + //must not consume thread death + result = handleException(currentJob, e); + throw e; + } catch (Error e) { + result = handleException(currentJob, e); + } finally { + if (result != Job.ASYNC_FINISH && monitor != null) { + monitor.done(); + } + //clear interrupted state for this thread + Thread.interrupted(); + //result must not be null + if (result == null) { + String message = NLS.bind(JobMessages.jobs_returnNoStatus, currentJob.getClass().getName()); + result = handleException(currentJob, new NullPointerException(message)); + } + pool.endJob(currentJob, result); + currentJob = null; + //reset thread priority in case job changed it + setPriority(Thread.NORM_PRIORITY); + } + } + } catch (Throwable t) { + RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "Unhandled error", t)); //$NON-NLS-1$ + } finally { + currentJob = null; + pool.endWorker(this); + } + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java new file mode 100644 index 0000000000..ee38ffdef7 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/WorkerPool.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * 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.Assert; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.jobs.Job; + +/** + * Maintains a pool of worker threads. Threads are constructed lazily as + * required, and are eventually discarded if not in use for awhile. This class + * maintains the thread creation/destruction policies for the job manager. + * + * Implementation note: all the data structures of this class are protected + * by the instance's object monitor. To avoid deadlock with third party code, + * this lock is never held when calling methods outside this class that may in + * turn use locks. + */ +class WorkerPool { + /** + * Threads not used by their best before timestamp are destroyed. + */ + private static final int BEST_BEFORE = 60000; + /** + * There will always be at least MIN_THREADS workers in the pool. + */ + private static final int MIN_THREADS = 1; + /** + * Use the busy thread count to avoid starting new threads when a living + * thread is just doing house cleaning (notifying listeners, etc). + */ + private int busyThreads = 0; + + /** + * The default context class loader to use when creating worker threads. + */ + protected final ClassLoader defaultContextLoader; + + /** + * Records whether new worker threads should be daemon threads. + */ + private boolean isDaemon = false; + + private JobManager manager; + /** + * The number of workers in the threads array + */ + private int numThreads = 0; + /** + * The number of threads that are currently sleeping + */ + private int sleepingThreads = 0; + /** + * The living set of workers in this pool. + */ + private Worker[] threads = new Worker[10]; + + protected WorkerPool(JobManager manager) { + this.manager = manager; + this.defaultContextLoader = Thread.currentThread().getContextClassLoader(); + } + + /** + * Adds a worker to the list of workers. + */ + private synchronized void add(Worker worker) { + int size = threads.length; + if (numThreads + 1 > size) { + Worker[] newThreads = new Worker[2 * size]; + System.arraycopy(threads, 0, newThreads, 0, size); + threads = newThreads; + } + threads[numThreads++] = worker; + } + + private synchronized void decrementBusyThreads() { + //impossible to have less than zero busy threads + if (--busyThreads < 0) { + if (JobManager.DEBUG) + Assert.isTrue(false, Integer.toString(busyThreads)); + busyThreads = 0; + } + } + + /** + * Signals the end of a job. Note that this method can be called under + * OutOfMemoryError conditions and thus must be paranoid about allocating objects. + */ + protected void endJob(InternalJob job, IStatus result) { + try { + //need to end rule in graph before ending job so that 2 threads + //do not become the owners of the same rule in the graph + if ((job.getRule() != null) && !(job instanceof ThreadJob)) { + //remove any locks this thread may be owning on that rule + manager.getLockManager().removeLockCompletely(Thread.currentThread(), job.getRule()); + } + manager.endJob(job, result, true); + //ensure this thread no longer owns any scheduling rules + manager.implicitJobs.endJob(job); + } finally { + decrementBusyThreads(); + } + } + + /** + * Signals the death of a worker thread. Note that this method can be called under + * OutOfMemoryError conditions and thus must be paranoid about allocating objects. + */ + protected synchronized void endWorker(Worker worker) { + if (remove(worker) && JobManager.DEBUG) + JobManager.debug("worker removed from pool: " + worker); //$NON-NLS-1$ + } + + private synchronized void incrementBusyThreads() { + //impossible to have more busy threads than there are threads + if (++busyThreads > numThreads) { + if (JobManager.DEBUG) + Assert.isTrue(false, Integer.toString(busyThreads) + ',' + numThreads); + busyThreads = numThreads; + } + } + + /** + * Notification that a job has been added to the queue. Wake a worker, + * creating a new worker if necessary. The provided job may be null. + */ + protected synchronized void jobQueued() { + //if there is a sleeping thread, wake it up + if (sleepingThreads > 0) { + notify(); + return; + } + //create a thread if all threads are busy + if (busyThreads >= numThreads) { + Worker worker = new Worker(this); + worker.setDaemon(isDaemon); + add(worker); + if (JobManager.DEBUG) + JobManager.debug("worker added to pool: " + worker); //$NON-NLS-1$ + worker.start(); + return; + } + } + + /** + * Remove a worker thread from our list. + * @return true if a worker was removed, and false otherwise. + */ + private synchronized boolean remove(Worker worker) { + for (int i = 0; i < threads.length; i++) { + if (threads[i] == worker) { + System.arraycopy(threads, i + 1, threads, i, numThreads - i - 1); + threads[--numThreads] = null; + return true; + } + } + return false; + } + + /** + * Sets whether threads created in the worker pool should be daemon threads. + */ + void setDaemon(boolean value) { + this.isDaemon = value; + } + + protected synchronized void shutdown() { + notifyAll(); + } + + /** + * Sleep for the given duration or until woken. + */ + private synchronized void sleep(long duration) { + sleepingThreads++; + busyThreads--; + if (JobManager.DEBUG) + JobManager.debug("worker sleeping for: " + duration + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ + try { + wait(duration); + } catch (InterruptedException e) { + if (JobManager.DEBUG) + JobManager.debug("worker interrupted while waiting... :-|"); //$NON-NLS-1$ + } finally { + sleepingThreads--; + busyThreads++; + } + } + + /** + * Returns a new job to run. Returns null if the thread should die. + */ + protected InternalJob startJob(Worker worker) { + //if we're above capacity, kill the thread + synchronized (this) { + if (!manager.isActive()) { + //must remove the worker immediately to prevent all threads from expiring + endWorker(worker); + return null; + } + //set the thread to be busy now in case of reentrant scheduling + incrementBusyThreads(); + } + Job job = null; + try { + job = manager.startJob(worker); + //spin until a job is found or until we have been idle for too long + long idleStart = System.currentTimeMillis(); + while (manager.isActive() && job == null) { + long hint = manager.sleepHint(); + if (hint > 0) + sleep(Math.min(hint, BEST_BEFORE)); + job = manager.startJob(worker); + //if we were already idle, and there are still no new jobs, then + // the thread can expire + synchronized (this) { + if (job == null && (System.currentTimeMillis() - idleStart > BEST_BEFORE) && (numThreads - busyThreads) > MIN_THREADS) { + //must remove the worker immediately to prevent all threads from expiring + endWorker(worker); + return null; + } + } + //if we didn't sleep but there was no job available, make sure we sleep to avoid a tight loop (bug 260724) + if (hint <= 0 && job == null) + sleep(50); + } + if (job != null) { + //if this job has a rule, then we are essentially acquiring a lock + if ((job.getRule() != null) && !(job instanceof ThreadJob)) { + //don't need to re-acquire locks because it was not recorded in the graph + //that this thread waited to get this rule + manager.getLockManager().addLockThread(Thread.currentThread(), job.getRule()); + } + //see if we need to wake another worker + if (manager.sleepHint() < InternalJob.T_INFINITE) + jobQueued(); + } + } finally { + //decrement busy thread count if we're not running a job + if (job == null) + decrementBusyThreads(); + } + return job; + } +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.properties b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.properties new file mode 100644 index 0000000000..3ce2b43e77 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/messages.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 +############################################################################### +### Runtime jobs plugin messages + +### Job Manager and Locks +jobs_blocked0=The user operation is waiting for background work to complete. +jobs_blocked1=The user operation is waiting for \"{0}\" to complete. +jobs_internalError=An internal error occurred during: \"{0}\". +jobs_waitFamSub={0} operations remaining. +jobs_waitFamSubOne={0} operation remaining. +jobs_returnNoStatus=Job of type {0} returned no status. + +### metadata +meta_pluginProblems = Problems occurred when invoking code from plug-in: \"{0}\". diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java new file mode 100644 index 0000000000..5cbf0839a6 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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.runtime.jobs; + +import org.eclipse.core.runtime.IStatus; + +/** + * An event describing a change to the state of a job. + * + * @see IJobChangeListener + * @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 IJobChangeEvent { + /** + * The amount of time in milliseconds to wait after scheduling the job before it + * should be run, or -1 if not applicable for this type of event. + * This value is only applicable for the scheduled event. + * + * @return the delay time for this event + */ + public long getDelay(); + + /** + * The job on which this event occurred. + * + * @return the job for this event + */ + public Job getJob(); + + /** + * The result returned by the job's run method, or null if + * not applicable. This value is only applicable for the done event. + * + * @return the status for this event + */ + public IStatus getResult(); + + /** + * 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. This value is only applicable for the done event. + * + * @return the job group status for this event, or null + * @since 3.7 + */ + public IStatus getJobGroupResult(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java new file mode 100644 index 0000000000..26ee6e1673 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeListener.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * 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; + +/** + * Callback interface for clients interested in being notified when jobs change state. + *

                          + * A single job listener instance can be added either to the job manager, for notification + * of all scheduled jobs, or to any set of individual jobs. A single listener instance should + * not be added to both the job manager, and to individual jobs (such a listener may + * receive duplicate notifications). + *

                          + * Clients should not rely on the result of the Job#getState() + * method on jobs for which notification is occurring. Listeners are notified of + * all job state changes, but whether the state change occurs before, during, or + * after listeners are notified is unspecified. + *

                          + * Clients may implement this interface. + *

                          + * @see JobChangeAdapter + * @see IJobManager#addJobChangeListener(IJobChangeListener) + * @see IJobManager#removeJobChangeListener(IJobChangeListener) + * @see Job#addJobChangeListener(IJobChangeListener) + * @see Job#getState() + * @see Job#removeJobChangeListener(IJobChangeListener) + * @since 3.0 + */ +public interface IJobChangeListener { + /** + * Notification that a job is about to be run. Listeners are allowed to sleep, cancel, + * or change the priority of the job before it is started (and as a result may prevent + * the run from actually occurring). + * + * @param event the event details + */ + public void aboutToRun(IJobChangeEvent event); + + /** + * Notification that a job was previously sleeping and has now been rescheduled + * to run. + * + * @param event the event details + */ + public void awake(IJobChangeEvent event); + + /** + * Notification that a job has completed execution, either due to cancelation, successful + * completion, or failure. The event status object indicates how the job finished, + * and the reason for failure, if applicable. + * + * @param event the event details + */ + public void done(IJobChangeEvent event); + + /** + * Notification that a job has started running. + * + * @param event the event details + */ + public void running(IJobChangeEvent event); + + /** + * Notification that a job is being added to the queue of scheduled jobs. + * The event details includes the scheduling delay before the job should start + * running. + * + * @param event the event details, including the job instance and the scheduling + * delay + */ + public void scheduled(IJobChangeEvent event); + + /** + * Notification that a job was waiting to run and has now been put in the + * sleeping state. + * + * @param event the event details + */ + public void sleeping(IJobChangeEvent event); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java new file mode 100644 index 0000000000..0cadd6b67c --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobFunction.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2014, 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 + * Lars Vogel - Bug 474276 + *******************************************************************************/ +package org.eclipse.core.runtime.jobs; + +import org.eclipse.core.runtime.*; + +/** + * This is a functional interface representation of {@link Job}, suitable + * for use in lambda expressions. + * + * @see Job#create(String, IJobFunction) + * @since 3.6 + */ +public interface IJobFunction { + /** + * 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 Job#ASYNC_FINISH}. Jobs + * that finish asynchronously must specify the execution thread by + * calling {@link Job#setThread(Thread)}, and must indicate when they are + * finished by calling the method {@link Job#done(IStatus)}. + * + * @param monitor + * the monitor to be used for reporting progress and responding + * to cancellation. The monitor is never {@code null}. It is the + * caller's responsibility to call + * {@link IProgressMonitor#done()} after this method returns or + * throws an exception. + * + * @return resulting status of the run. The result must not be {@code null}. + * @see Job#ASYNC_FINISH + * @see Job#done(IStatus) + */ + public IStatus run(IProgressMonitor monitor); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java new file mode 100644 index 0000000000..454bcef845 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobManager.java @@ -0,0 +1,426 @@ +/******************************************************************************* + * 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.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +/** + * The job manager provides facilities for scheduling, querying, and maintaining jobs + * and locks. In particular, the job manager provides the following services: + *

                            + *
                          • Maintains a queue of jobs that are waiting to be run. Items can be added to + * the queue using the schedule method.
                          • + *
                          • Allows manipulation of groups of jobs called job families. Job families can + * be canceled, put to sleep, or woken up atomically. There is also a mechanism + * for querying the set of known jobs in a given family.
                          • + *
                          • Allows listeners to find out about progress on running jobs, and to find out + * when jobs have changed states.
                          • + *
                          • Provides a factory for creating lock objects. Lock objects are smart monitors + * that have strategies for avoiding deadlock.
                          • + *
                          • Provide feedback to a client that is waiting for a given job or family of jobs + * to complete.
                          • + *
                          + * + * @see Job + * @see ILock + * @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 IJobManager { + /** + * A system property key indicating whether the job manager should create + * job threads as daemon threads. Set to true to force all worker + * threads to be created as daemon threads. Set to false to force + * all worker threads to be created as non-daemon threads. + * @since 3.3 + */ + public static final String PROP_USE_DAEMON_THREADS = "eclipse.jobs.daemon"; //$NON-NLS-1$ + + /** + * Registers a job listener with the job manager. + * Has no effect if an identical listener is already registered. + * + * @param listener the listener to be added + * @see #removeJobChangeListener(IJobChangeListener) + * @see IJobChangeListener + */ + public void addJobChangeListener(IJobChangeListener listener); + + /** + * Begins applying this rule in the calling thread. If the rule conflicts with another + * rule currently running in another thread, this method blocks until there are + * no conflicting rules. Calls to beginRule must eventually be followed + * by a matching call to endRule in the same thread and with the identical + * rule instance. + *

                          + * Rules can be nested only if the rule for the inner beginRule + * is contained within the rule for the outer beginRule. Rule containment + * is tested with the API method ISchedulingRule.contains. Also, begin/end + * pairs must be strictly nested. Only the rule that has most recently begun + * can be ended at any given time. + *

                          + * A rule of null can be used, but will be ignored for scheduling + * purposes. The outermost non-null rule in the thread will be used for scheduling. A + * null rule that is begun must still be ended. + *

                          + * If this method is called from within a job that has a scheduling rule, the + * given rule must also be contained within the rule for the running job. + *

                          + * Note that endRule must be called even if beginRule fails. + * The recommended usage is: + *

                          +	 * final ISchedulingRule rule = ...;
                          +	 * try {
                          +	 * 	manager.beginRule(rule, monitor);
                          +	 * } finally {
                          +	 * 	manager.endRule(rule);
                          +	 * }
                          +	 * 
                          + * + * @param rule the rule to begin applying in this thread, or null + * @param monitor a progress monitor, or null if progress + * reporting and cancellation are not desired + * @throws IllegalArgumentException if the rule is not strictly nested within + * all other rules currently active for this thread + * @throws OperationCanceledException if the supplied monitor reports cancelation + * before the rule becomes available + * @see ISchedulingRule#contains(ISchedulingRule) + */ + public void beginRule(ISchedulingRule rule, IProgressMonitor monitor); + + /** + * Cancels all jobs in the given job family. Jobs in the family that are currently waiting + * 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. + * + * @param family the job family to cancel, or null to cancel all jobs + * @see Job#belongsTo(Object) + */ + public void cancel(Object family); + + /** + * Returns a progress monitor that can be used to provide + * aggregated progress feedback on a set of running jobs. A user + * interface will typically group all jobs in a progress group together, + * providing progress feedback for individual jobs as well as aggregated + * progress for the entire group. Jobs in the group may be run sequentially, + * in parallel, or some combination of the two. + *

                          + * Recommended usage (this snippet runs two jobs in sequence in a + * single progress group): + *

                          +	 *    Job parseJob, compileJob;
                          +	 *    IProgressMonitor pm = Platform.getJobManager().createProgressGroup();
                          +	 *    try {
                          +	 *       pm.beginTask("Building", 10);
                          +	 *       parseJob.setProgressGroup(pm, 5);
                          +	 *       parseJob.schedule();
                          +	 *       compileJob.setProgressGroup(pm, 5);
                          +	 *       compileJob.schedule();
                          +	 *       parseJob.join();
                          +	 *       compileJob.join();
                          +	 *    } finally {
                          +	 *       pm.done();
                          +	 *    }
                          +	 * 
                          + * + * @see Job#setProgressGroup(IProgressMonitor, int) + * @see IProgressMonitor + * @return a progress monitor + */ + public IProgressMonitor createProgressGroup(); + + /** + * Returns the scheduling rule currently held by this thread, or null + * if the current thread does not hold any scheduling rule. + *

                          + * If this method is called from within the scope of a running job with a non-null + * scheduling rule, then this method is equivalent to calling currentJob().getRule(). + * Otherwise, this method will return the first scheduling rule obtained by this + * thread via {@link #beginRule(ISchedulingRule, IProgressMonitor)} that has not + * yet had a corresponding call to {@link #endRule(ISchedulingRule)}. + *

                          + * + * @return the current rule or null + * @since 3.5 + */ + public ISchedulingRule currentRule(); + + /** + * Returns the job that is currently running in this thread, or null if there + * is no currently running job. + * + * @return the job or null + */ + public Job currentJob(); + + /** + * Ends the application of a rule to the calling thread. Calls to endRule + * must be preceded by a matching call to beginRule in the same thread + * with an identical rule instance. + *

                          + * Rules can be nested only if the rule for the inner beginRule + * is contained within the rule for the outer beginRule. Also, begin/end + * pairs must be strictly nested. Only the rule that has most recently begun + * can be ended at any given time. + * + * @param rule the rule to end applying in this thread + * @throws IllegalArgumentException if this method is called on a rule for which + * there is no matching begin, or that does not match the most recent begin. + * @see ISchedulingRule#contains(ISchedulingRule) + */ + public void endRule(ISchedulingRule rule); + + /** + * Returns all waiting, executing and sleeping jobs belonging + * to the given family. If no jobs are found, an empty array is returned. + * + * @param family the job family to find, or null to find all jobs + * @return the job array + * @see Job#belongsTo(Object) + */ + public Job[] find(Object family); + + /** + * Returns whether the job manager is currently idle. The job manager is + * idle if no jobs are currently running or waiting to run. + * + * @return true if the job manager is idle, and + * false otherwise + * @since 3.1 + */ + public boolean isIdle(); + + /** + * Returns whether the job manager is currently suspended. + * + * @return true if the job manager is suspended, and + * false otherwise + * @since 3.4 + * @see #suspend() + * @see #resume() + */ + public boolean isSuspended(); + + /** + * Waits until all jobs of the given family are finished. This method will block the + * calling thread until all such jobs have finished executing, or until this thread is + * interrupted. If there are no jobs in the family that are currently waiting, running, + * or sleeping, this method returns immediately. Feedback on how the join is + * progressing is provided to a 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 + * in the family in the {@link Job#RUNNING} state, this method returns. + *

                          + *

                          + * 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. This method can also result in starvation of the current thread if + * another thread continues to add jobs of the given family, or if a + * job in the given family reschedules itself in an infinite loop. + *

                          + * + * @param family the job family to join, or null to join all jobs. + * @param monitor Progress monitor for reporting progress on how the + * wait is progressing, or null if no progress monitoring is required. + * @exception InterruptedException if this thread is interrupted while waiting + * @exception OperationCanceledException if the progress monitor is canceled while waiting + * @see Job#belongsTo(Object) + * @see #suspend() + */ + public void join(Object family, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException; + + /** + * Creates a new lock object. All lock objects supplied by the job manager + * know about each other and will always avoid circular deadlock amongst + * themselves. + * + * @return the new lock object + */ + public ILock newLock(); + + /** + * Removes a job listener from the job manager. + * Has no effect if an identical listener is not already registered. + * + * @param listener the listener to be removed + * @see #addJobChangeListener(IJobChangeListener) + * @see IJobChangeListener + */ + public void removeJobChangeListener(IJobChangeListener listener); + + /** + * Resumes execution of jobs after a previous suspend. All + * jobs that were sleeping or waiting prior to the suspension, or that were + * scheduled while the job manager was suspended, will now be eligible + * for execution. + *

                          + * Calling this method on a rule that is not suspended has no effect. If another + * thread also owns the rule at the time this method is called, then the rule will + * not be resumed until all threads have released the rule. + * + * @deprecated This method is not safe and should not be used. + * Suspending a scheduling rule violates the thread safety + * of clients that use scheduling rules as a mutual exclusion mechanism, + * and can result in concurrency problems in all clients that use the suspended rule. + * @see #suspend(ISchedulingRule, IProgressMonitor) + */ + @Deprecated + public void resume(ISchedulingRule rule); + + /** + * Resumes execution of jobs after a previous suspend. All + * jobs that were sleeping or waiting prior to the suspension, or that were + * scheduled while the job manager was suspended, will now be eligible + * for execution. + *

                          + * Calling resume when the job manager is not suspended + * has no effect. + * + * @see #suspend() + * @see #isSuspended() + */ + public void resume(); + + /** + * Provides a hook that is notified whenever a thread is about to wait on a lock, + * or when a thread is about to release a lock. This hook must only be set once. + *

                          + * This method is for internal use by the platform-related plug-ins. + * Clients should not call this method. + *

                          + * @see LockListener + */ + public void setLockListener(LockListener listener); + + /** + * Registers a progress provider with the job manager. If there was a + * provider already registered, it is replaced. + *

                          + * This method is intended for use by the currently executing Eclipse application. + * Plug-ins outside the currently running application should not call this method. + *

                          + * + * @param provider the new provider, or null if no progress + * is needed + */ + public void setProgressProvider(ProgressProvider provider); + + /** + * Suspends execution of all jobs. Jobs that are already running + * when this method is invoked will complete as usual, but all sleeping and + * waiting jobs will not be executed until the job manager is resumed. + *

                          + * The job manager will remain suspended until a subsequent call to + * resume. Further calls to suspend + * when the job manager is already suspended are ignored. + *

                          + * All attempts to join sleeping and waiting jobs while the job manager is + * suspended will return immediately. + *

                          + * Note that this very powerful function should be used with extreme caution. + * Suspending the job manager will prevent all jobs in the system from executing, + * which may have adverse affects on components that are relying on + * execution of jobs. The job manager should never be suspended without intent + * to resume execution soon afterwards. + * + * @see #resume() + * @see #join(Object, IProgressMonitor) + * @see #isSuspended() + */ + public void suspend(); + + /** + * Defers execution of all jobs with scheduling rules that conflict with the + * given rule. The caller will be blocked until all currently executing jobs with + * conflicting rules are completed. Conflicting jobs that are sleeping or waiting at + * the time this method is called will not be executed until the rule is resumed. + *

                          + * While a rule is suspended, all calls to beginRule and + * endRule on a suspended rule will not block the caller. + * The rule remains suspended until a subsequent call to + * resume(ISchedulingRule) with the identical rule instance. + * Further calls to suspend with an identical rule prior to calling + * resume are ignored. + *

                          + *

                          + * This method is long-running; progress and cancelation are provided by + * the given progress monitor. In the case of cancelation, the rule will + * not be suspended. + *

                          + * Note: this very powerful function should be used with extreme caution. + * Suspending rules will prevent jobs in the system from executing, which may + * have adverse effects on components that are relying on execution of jobs. + * The job manager should never be suspended without intent to resume + * execution soon afterwards. Deadlock will result if the thread responsible + * for resuming the rule attempts to join a suspended job. + * + * @deprecated This method is not safe and should not be used. + * Suspending a scheduling rule violates the thread safety + * of clients that use scheduling rules as a mutual exclusion mechanism, + * and can result in concurrency problems in all clients that use the suspended rule. + * @param rule The scheduling rule to suspend. Must not be null. + * @param monitor a progress monitor, or null if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #resume(ISchedulingRule) + */ + @Deprecated + public void suspend(ISchedulingRule rule, IProgressMonitor monitor); + + /** + * Requests that all jobs in the given job family be suspended. Jobs currently + * waiting to be run will be removed from the queue and moved into the + * SLEEPING state. Jobs that have been put to sleep + * will remain in that state until either resumed or canceled. This method has + * no effect on jobs that are not currently waiting to be run. + *

                          + * Sleeping jobs can be resumed using wakeUp. + * + * @param family the job family to sleep, or null to sleep all jobs. + * @see Job#belongsTo(Object) + */ + public void sleep(Object family); + + /** + * Transfers ownership of a scheduling rule to another thread. The identical + * scheduling rule must currently be owned by the calling thread as a result of + * a previous call to beginRule. The destination thread must + * not already own a scheduling rule. + *

                          + * Calling this method is equivalent to atomically calling endRule + * in the calling thread followed by an immediate beginRule in + * the destination thread. The destination thread is responsible for subsequently + * calling endRule when it is finished using the rule. + *

                          + * This method has no effect when the destination thread is the same as the + * calling thread. + * + * @param rule The scheduling rule to transfer + * @param destinationThread The new owner for the transferred rule. + * @since 3.1 + */ + public void transferRule(ISchedulingRule rule, Thread destinationThread); + + /** + * Resumes scheduling of all sleeping jobs in the given family. This method + * has no effect on jobs in the family that are not currently sleeping. + * + * @param family the job family to wake up, or null to wake up all jobs + * @see Job#belongsTo(Object) + */ + public void wakeUp(Object family); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java new file mode 100644 index 0000000000..07cd46299f --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobStatus.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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.runtime.IStatus; + +/** + * Represents status relating to the execution of jobs. + * + * @see org.eclipse.core.runtime.IStatus + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IJobStatus extends IStatus { + /** + * Returns the job associated with this status. + * + * @return the job associated with this status + */ + public Job getJob(); +} diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java new file mode 100644 index 0000000000..d19276e6fd --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ILock.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * 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; + +/** + * A lock is used to control access to an exclusive resource. + *

                          + * Locks are reentrant. That is, they can be acquired multiple times by the same thread + * without releasing. Locks are only released when the number of successful acquires + * equals the number of successful releases. + *

                          + * Locks are capable of detecting and recovering from programming errors that cause + * circular waiting deadlocks. When a deadlock between two or more ILock + * instances is detected, detailed debugging information is printed to the log file. The + * locks will then automatically recover from the deadlock by employing a release + * and wait strategy. One thread will lose control of the locks it owns, thus breaking + * the deadlock and allowing other threads to proceed. Once that thread's locks are + * all available, it will be given exclusive access to all its locks and allowed to proceed. + * A thread can only lose locks while it is waiting on an acquire() call. + * + *

                          + * Successive acquire attempts by different threads are queued and serviced on + * a first come, first served basis. + *

                          + * It is very important that acquired locks eventually get released. Calls to release + * should be done in a finally block to ensure they execute. For example: + *

                          + * try {
                          + * 	lock.acquire();
                          + * 	// ... do work here ...
                          + * } finally {
                          + * 	lock.release();
                          + * }
                          + * 
                          + * Note: although lock.acquire should never fail, it is good practice to place + * it inside the try block anyway. Releasing without acquiring is far less catastrophic + * than acquiring without releasing. + *

                          + * + * @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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ISchedulingRule.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java new file mode 100644 index 0000000000..1fe7077ed8 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java @@ -0,0 +1,938 @@ +/******************************************************************************* + * 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 + * 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 that will execute the provided runnable when it runs. + * + * @param name + * the name of the job + * @param runnable + * the runnable to execute + * @return a job that encapsulates the provided runnable + * @see ICoreRunnable + * @since 3.8 + */ + public static Job create(String name, final ICoreRunnable runnable) { + return new Job(name) { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + runnable.run(monitor); + } catch (CoreException e) { + IStatus st = e.getStatus(); + return new Status(st.getSeverity(), st.getPlugin(), st.getCode(), st.getMessage(), e); + } + return Status.OK_STATUS; + } + }; + } + + /** + * Creates a new system {@link Job} with the given name that will execute + * the provided runnable when it runs. + * + * @param name + * the name of the job + * @param runnable + * the runnable to execute + * @return a job that encapsulates the provided runnable + * @see ICoreRunnable + * @see Job#setSystem(boolean) + * @since 3.8 + */ + public static Job createSystem(String name, final ICoreRunnable runnable) { + Job job = create(name, runnable); + job.setSystem(true); + return job; + } + + /** + * Creates a new system {@link Job} that will execute the provided runnable + * when it runs. + * + * @param runnable + * the runnable to execute + * @return a job that encapsulates the provided runnable + * @see ICoreRunnable + * @see Job#setSystem(boolean) + * @since 3.8 + */ + public static Job createSystem(final ICoreRunnable runnable) { + return createSystem("", runnable); //$NON-NLS-1$ + } + + /** + * 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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobChangeAdapter.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobGroup.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobGroup.java new file mode 100644 index 0000000000..abade10342 --- /dev/null +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/JobGroup.java @@ -0,0 +1,277 @@ +/******************************************************************************* + * 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. + *

                          + *

                          + * NOTE: If the job manager is suspended, the result of the job group + * run may not be set when this method returns. + *

                          + * + * @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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/LockListener.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/MultiRule.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/MultiRule.java new file mode 100644 index 0000000000..cb03b58998 --- /dev/null +++ b/third_party/patches/oxygen/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 (ISchedulingRule element : ruleArray) { + if (element == null) + continue; + if (result == null) { + result = element; + continue; + } + result = combine(result, element); + } + 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 (ISchedulingRule nestedRule : nestedRules) { + if (nestedRule instanceof MultiRule) { + ISchedulingRule[] children = ((MultiRule) nestedRule).getChildren(); + for (ISchedulingRule element : children) + myRules.add(element); + } else { + myRules.add(nestedRule); + } + } + 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 (ISchedulingRule otherRule : otherRules) { + boolean found = false; + for (int mine = 0; !found && mine < rules.length; mine++) + found = rules[mine].contains(otherRule); + if (!found) + return false; + } + return true; + } + for (ISchedulingRule rule2 : rules) + if (rule2.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 (ISchedulingRule otherRule : otherRules) + for (ISchedulingRule rule2 : rules) + if (rule2.isConflicting(otherRule)) + return true; + } else { + for (ISchedulingRule rule3 : rules) + if (rule3.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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/ProgressProvider.java b/third_party/patches/oxygen/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/oxygen/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/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/package.html b/third_party/patches/oxygen/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/oxygen/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. +

                          +@since 3.0 +

                          + + diff --git a/third_party/patches/oxygen/org.eclipse.core.resources/META-INF/MANIFEST.MF b/third_party/patches/oxygen/org.eclipse.core.resources/META-INF/MANIFEST.MF index 1c141935b9..892983404f 100644 --- a/third_party/patches/oxygen/org.eclipse.core.resources/META-INF/MANIFEST.MF +++ b/third_party/patches/oxygen/org.eclipse.core.resources/META-INF/MANIFEST.MF @@ -1,6 +1,6 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 -Bundle-Name: %pluginName +Bundle-Name: org.eclipse.core.resources patched for bug X Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true Bundle-Version: 3.12.0.v20170417-1558 Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin diff --git a/third_party/patches/oxygen/pom.xml b/third_party/patches/oxygen/pom.xml index 300c12cdf6..86ac269c80 100644 --- a/third_party/patches/oxygen/pom.xml +++ b/third_party/patches/oxygen/pom.xml @@ -16,6 +16,7 @@ Patches bundles for Eclipse Oxygen - org.eclipse.core.resources + + org.eclipse.core.jobs From 484df42bbbef5d3abcc1b68165c965cd77de9659 Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Tue, 22 Aug 2017 10:07:43 -0400 Subject: [PATCH 9/9] Apply patch https://git.eclipse.org/c/56954/7 to org.eclipse.core.jobs --- .../eclipse/core/internal/jobs/ThreadJob.java | 8 ++++-- .../eclipse/core/internal/jobs/ThreadJob.java | 8 ++++-- .../META-INF/MANIFEST.MF | 2 +- .../oxygen/org.eclipse.core.jobs/pom.xml | 28 ------------------- .../eclipse/core/internal/jobs/ThreadJob.java | 8 ++++-- 5 files changed, 16 insertions(+), 38 deletions(-) delete mode 100644 third_party/patches/oxygen/org.eclipse.core.jobs/pom.xml diff --git a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java index 1567ac0bcd..aa107693a2 100644 --- a/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java +++ b/third_party/patches/mars/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java @@ -288,9 +288,11 @@ else if (state != Job.NONE) } finally { if (interrupted) Thread.currentThread().interrupt(); - //only update the lock state if we ended up using the thread job that was given to us - waitEnd(threadJob, threadJob == result, monitor); - if (threadJob == result) { + ISchedulingRule resultRule = result.getRule(); + ISchedulingRule threadJobRule = threadJob.getRule(); + boolean containsRule = resultRule != null && threadJobRule != null && resultRule.contains(threadJobRule); + waitEnd(threadJob, containsRule, monitor); + if (containsRule) { if (waiting) manager.implicitJobs.removeWaiting(threadJob); } diff --git a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java index 1567ac0bcd..aa107693a2 100644 --- a/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java +++ b/third_party/patches/neon/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java @@ -288,9 +288,11 @@ else if (state != Job.NONE) } finally { if (interrupted) Thread.currentThread().interrupt(); - //only update the lock state if we ended up using the thread job that was given to us - waitEnd(threadJob, threadJob == result, monitor); - if (threadJob == result) { + ISchedulingRule resultRule = result.getRule(); + ISchedulingRule threadJobRule = threadJob.getRule(); + boolean containsRule = resultRule != null && threadJobRule != null && resultRule.contains(threadJobRule); + waitEnd(threadJob, containsRule, monitor); + if (containsRule) { if (waiting) manager.implicitJobs.removeWaiting(threadJob); } diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/META-INF/MANIFEST.MF b/third_party/patches/oxygen/org.eclipse.core.jobs/META-INF/MANIFEST.MF index 782b367148..c9d818f85f 100644 --- a/third_party/patches/oxygen/org.eclipse.core.jobs/META-INF/MANIFEST.MF +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ 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.9.1.qualifier +Bundle-Version: 3.9.0.v20170322-0013 Bundle-Vendor: %providerName Bundle-Localization: plugin Export-Package: org.eclipse.core.internal.jobs;x-internal:=true, diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/pom.xml b/third_party/patches/oxygen/org.eclipse.core.jobs/pom.xml deleted file mode 100644 index 70d888f2be..0000000000 --- a/third_party/patches/oxygen/org.eclipse.core.jobs/pom.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - 4.0.0 - - eclipse.platform.runtime - eclipse.platform.runtime - 4.7.1-SNAPSHOT - ../../ - - org.eclipse.core - org.eclipse.core.jobs - 3.9.1-SNAPSHOT - eclipse-plugin - - -warn:-deprecation,raw,unchecked - - diff --git a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java index 5f87980755..63e656d5b9 100644 --- a/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java +++ b/third_party/patches/oxygen/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java @@ -303,9 +303,11 @@ else if (state != Job.NONE) manager.getLockManager().removeLockWaitThread(currentThread, threadJob.getRule()); } } finally { - //only update the lock state if we ended up using the thread job that was given to us - waitEnd(threadJob, threadJob == result, monitor); - if (threadJob == result) { + ISchedulingRule resultRule = result.getRule(); + ISchedulingRule threadJobRule = threadJob.getRule(); + boolean containsRule = resultRule != null && threadJobRule != null && resultRule.contains(threadJobRule); + waitEnd(threadJob, containsRule, monitor); + if (containsRule) { if (waiting) manager.implicitJobs.removeWaiting(threadJob); }