# HG changeset patch
# User Steve Sobek June 2, 2006 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.
+ *
+ * Internally, data trees can be either complete trees (DataTree class), or delta
+ * trees ( 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 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/AbstractDataTreeNode.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,551 @@
+/*******************************************************************************
+ * 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.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 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) {
+
+ AbstractDataTreeNode[] simplifiedNodes = new AbstractDataTreeNode[nodes.length];
+ int simplifiedCount = 0;
+ for (int i = 0; i < nodes.length; ++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)
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataDeltaNode.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * A Nodes that exist in the parent but do not exist in the delta, are represented
+ * as instances of 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.
+ */
+ 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
+ */
+ 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)
+ */
+ public void createChild(IPath parentKey, String localName) {
+ createChild(parentKey, localName, null);
+ }
+
+ /**
+ * @see AbstractDataTree#createChild(IPath, String, Object)
+ */
+ 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()
+ */
+ protected AbstractDataTree createInstance() {
+ return new DeltaDataTree();
+ }
+
+ /**
+ * @see AbstractDataTree#createSubtree(IPath, AbstractDataTreeNode)
+ */
+ 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)
+ */
+ 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()
+ */
+ 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)
+ */
+ 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)
+ */
+ 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.
+ */
+ 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)
+ */
+ 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)
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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 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
+ * Note that the
+ * 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceComparator.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,193 @@
+/*******************************************************************************
+ * 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.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.
+ */
+ 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 (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 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDelta.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,572 @@
+/*******************************************************************************
+ * 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 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;
+ }
+
+ /*
+ * @see IResourceDelta#accept(IResourceDeltaVisitor)
+ */
+ public void accept(IResourceDeltaVisitor visitor) throws CoreException {
+ accept(visitor, 0);
+ }
+
+ /*
+ * @see IResourceDelta#accept(IResourceDeltaVisitor, boolean)
+ */
+ public void accept(IResourceDeltaVisitor visitor, boolean includePhantoms) throws CoreException {
+ accept(visitor, includePhantoms ? IContainer.INCLUDE_PHANTOMS : 0);
+ }
+
+ /*
+ * @see IResourceDelta#accept(IResourceDeltaVisitor, int)
+ */
+ 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 = (MarkerSet) 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;
+ }
+ }
+ }
+
+ /**
+ * @see IResourceDelta#findMember(IPath)
+ */
+ 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);
+ }
+
+ /**
+ * @see IResourceDelta#getAffectedChildren()
+ */
+ public IResourceDelta[] getAffectedChildren() {
+ return getAffectedChildren(ADDED | REMOVED | CHANGED, IResource.NONE);
+ }
+
+ /**
+ * @see IResourceDelta#getAffectedChildren(int)
+ */
+ public IResourceDelta[] getAffectedChildren(int kindMask) {
+ return getAffectedChildren(kindMask, IResource.NONE);
+ }
+
+ /*
+ * @see IResourceDelta#getAffectedChildren(int, int)
+ */
+ 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;
+ }
+
+ /**
+ * @see IResourceDelta#getFlags()
+ */
+ public int getFlags() {
+ return status & ~KIND_MASK;
+ }
+
+ /**
+ * @see IResourceDelta#getFullPath()
+ */
+ public IPath getFullPath() {
+ return path;
+ }
+
+ /**
+ * @see IResourceDelta#getKind()
+ */
+ public int getKind() {
+ return status & KIND_MASK;
+ }
+
+ /**
+ * @see IResourceDelta#getMarkerDeltas()
+ */
+ public IMarkerDelta[] getMarkerDeltas() {
+ Map markerDeltas = deltaInfo.getMarkerDeltas();
+ if (markerDeltas == null)
+ return EMPTY_MARKER_DELTAS;
+ if (path == null)
+ path = Path.ROOT;
+ MarkerSet changes = (MarkerSet) 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;
+ }
+
+ /**
+ * @see IResourceDelta#getMovedFromPath()
+ */
+ public IPath getMovedFromPath() {
+ if ((status & MOVED_FROM) != 0) {
+ return deltaInfo.getNodeIDMap().getOldPath(newInfo.getNodeId());
+ }
+ return null;
+ }
+
+ /**
+ * @see IResourceDelta#getMovedToPath()
+ */
+ public IPath getMovedToPath() {
+ if ((status & MOVED_TO) != 0) {
+ return deltaInfo.getNodeIDMap().getNewPath(oldInfo.getNodeId());
+ }
+ return null;
+ }
+
+ /**
+ * @see IResourceDelta#getProjectRelativePath()
+ */
+ 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);
+ }
+
+ /**
+ * @see IResourceDelta#getResource()
+ */
+ 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
+ */
+ 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;
+ }
+ 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 = (IPath) e.next();
+ if (getResource().getFullPath().equals(key)) {
+ IMarkerSetElement[] deltas = ((MarkerSet) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaFactory.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,183 @@
+/*******************************************************************************
+ * 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 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaInfo.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceDeltaInfo.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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 java.util.Map;
+import org.eclipse.core.internal.resources.Workspace;
+
+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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceStats.java Mon Jun 01 10:16:20 2009 -0500
@@ -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
+ * The workspace paths of {@link IResource#HIDDEN} project and resources
+ * located in {@link IResource#HIDDEN} projects won't be added to the result.
+ *
+ * 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.
+ *
+ * Version 2 (3.1 M5):
+ *
+ * Version 1 (3.1 M4):
+ * About This Content
+
+License
+
+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.
+ *
+ * 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.
+ *
+ * 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)
+ */
+ 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);
+ }
+
+ 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.
+ */
+ 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.
+ */
+ boolean isDelta() {
+ return true;
+ }
+
+ /**
+ * Simplifies the given node, and answers its replacement.
+ */
+ 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)
+ */
+ 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.
+ */
+ int type() {
+ return T_DELTA_NODE;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTree.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,281 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * 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
+ */
+ 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)
+ */
+ public void createChild(IPath parentKey, String localName) {
+ createChild(parentKey, localName, null);
+ }
+
+ /**
+ * Creates a new child in the tree.
+ * @see AbstractDataTree#createChild(IPath, String, Object)
+ */
+ 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
+ */
+ 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.
+ */
+ 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)
+ */
+ 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()
+ */
+ 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.
+ */
+ 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)
+ */
+ 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
+ */
+ AbstractDataTreeNode getRootNode() {
+ return rootNode;
+ }
+
+ /**
+ * Returns true if the receiver includes a node with
+ * the given key, false otherwise.
+ */
+ 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.
+ */
+ 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)
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeLookup.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeLookup.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeNode.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,359 @@
+/*******************************************************************************
+ * 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.*;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * DataTreeNode
s 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)
+ */
+ 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.
+ */
+ 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);
+ }
+
+ 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.
+ */
+ 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
+ */
+ public Object getData() {
+ return data;
+ }
+
+ /**
+ * Returns true if the receiver can carry data, false otherwise.
+ */
+ boolean hasData() {
+ return true;
+ }
+
+ /**
+ * Sets the data for the node
+ */
+ void setData(Object o) {
+ data = o;
+ }
+
+ /**
+ * Simplifies the given node, and answers its replacement.
+ */
+ 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
+ */
+ 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)
+ */
+ 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.
+ */
+ int type() {
+ return T_COMPLETE_NODE;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * 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.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
+ */
+ protected AbstractDataTreeNode readNode(IPath parentPath) 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) {
+ 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);
+ }
+ }
+
+ /* 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
+ */
+ public DeltaDataTree readTree(DeltaDataTree parent, DataInput input) throws IOException {
+ this.input = input;
+ AbstractDataTreeNode root = readNode(Path.ROOT);
+ return new DeltaDataTree(root, parent);
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeletedNode.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * 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.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)
+ */
+ 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
+ */
+ 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
+ */
+ AbstractDataTreeNode childAtOrNull(String localName) {
+ /* deleted nodes do not have children */
+ return null;
+ }
+
+ 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.
+ */
+ AbstractDataTreeNode copy() {
+ return new DeletedNode(name);
+ }
+
+ /**
+ * Returns true if the receiver represents a deleted node, false otherwise.
+ */
+ boolean isDeleted() {
+ return true;
+ }
+
+ /**
+ * Simplifies the given node, and returns its replacement.
+ */
+ 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
+ */
+ 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)
+ */
+ public String toString() {
+ return "a DeletedNode(" + this.getName() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Returns a string describing the type of node.
+ */
+ int type() {
+ return T_DELETED_NODE;
+ }
+
+ AbstractDataTreeNode childAtIgnoreCase(String localName) {
+ /* deleted nodes do not have children */
+ return null;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DeltaDataTree.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,959 @@
+/*******************************************************************************
+ * 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.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.
+ *
+ * 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.
+ *
+ * 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)
+ */
+ 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
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IComparator.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java Mon Jun 01 10:16:20 2009 -0500
@@ -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.
+ * null
.
+ */
+ public void writeData(IPath path, Object data, DataOutput output) throws IOException;
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NoDataDeltaNode.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * A NoDataDeltaNode
is 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)
+ */
+ 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)
+ */
+ 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.
+ */
+ 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.
+ */
+ boolean isDelta() {
+ return true;
+ }
+
+ /**
+ * Returns true if the receiver is an empty delta node, false otherwise.
+ */
+ boolean isEmptyDelta() {
+ return this.size() == 0;
+ }
+
+ /**
+ * Simplifies the given node, and returns its replacement.
+ */
+ 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)
+ */
+ 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.
+ */
+ int type() {
+ return T_NO_DATA_DELTA_NODE;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NodeComparison.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/NodeComparison.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * 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 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
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/ObjectNotFoundException.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/TestHelper.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/TestHelper.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,277 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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 - 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.ResourceException;
+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.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;
+ }
+
+ 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("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)
+ new RuntimeException("Build Needed").printStackTrace(); //$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 :
+ setSystem(!isAutoBuilding);
+ 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)
+ System.out.println("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(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);
+ if (interrupted && Policy.DEBUG_BUILD_INTERRUPT) {
+ System.out.println("Autobuild was interrupted:"); //$NON-NLS-1$
+ new Exception().fillInStackTrace().printStackTrace();
+ }
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.Preferences.IPropertyChangeListener#propertyChange(org.eclipse.core.runtime.Preferences.PropertyChangeEvent)
+ */
+ 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);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.jobs.InternalJob#run(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ 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;
+ }
+
+ /**
+ * 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildCommand.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * 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 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 builder, including a reference
+ * to the builder instance itself if it has been instantiated.
+ */
+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;
+
+ /**
+ * The builder instance for this command. Null if the builder has
+ * not yet been instantiated.
+ */
+ protected IncrementalProjectBuilder builder;
+
+ /**
+ * 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$
+ this.arguments = new HashMap(0);
+ }
+
+ 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.setBuilder(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
+ */
+ 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 and equal argument tables
+ return getBuilderName().equals(command.getBuilderName()) && getArguments(false).equals(command.getArguments(false)) && triggers == command.triggers;
+ }
+
+ /**
+ * @see ICommand#getArguments()
+ */
+ public Map getArguments() {
+ return getArguments(true);
+ }
+
+ public Map getArguments(boolean makeCopy) {
+ return arguments == null ? null : (makeCopy ? (Map) arguments.clone() : arguments);
+ }
+
+ public IncrementalProjectBuilder getBuilder() {
+ return builder;
+ }
+
+ /**
+ * @see ICommand#getBuilderName()
+ */
+ public String getBuilderName() {
+ return getName();
+ }
+
+ /* (non-Javadoc)
+ * Method declared on Object
+ */
+ public int hashCode() {
+ // hash on name alone
+ return 37 * getName().hashCode() + triggers;
+ }
+
+ /**
+ * @see ICommand#isBuilding(int)
+ */
+ public boolean isBuilding(int trigger) {
+ return (triggers & maskForTrigger(trigger)) != 0;
+ }
+
+ public boolean isConfigurable() {
+ if ((triggers & MASK_CONFIG_COMPUTED) == 0)
+ computeIsConfigurable();
+ return (triggers & MASK_CONFIGURABLE) != 0;
+ }
+
+ /**
+ * @see ICommand#setArguments(Map)
+ */
+ public void setArguments(Map value) {
+ // copy parameter for safety's sake
+ arguments = value == null ? null : new HashMap(value);
+ }
+
+ public void setBuilder(IncrementalProjectBuilder builder) {
+ this.builder = builder;
+ }
+
+ /**
+ * @see ICommand#setBuilderName(String)
+ */
+ 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)
+ */
+ 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
+ */
+ public String toString() {
+ return "BuildCommand(" + getName() + ")";//$NON-NLS-1$ //$NON-NLS-2$
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,979 @@
+/*******************************************************************************
+ * 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
+ * Isaac Pacht (isaacp3@gmail.com) - fix for bug 206540
+ *******************************************************************************/
+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.ILock;
+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.
+ */
+ 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;
+ }
+ }
+
+ 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 ArrayList builtProjects = new ArrayList();
+
+ //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 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.clearForgetLastBuiltState();
+ // Figure out want kind of build is needed
+ boolean clean = trigger == IncrementalProjectBuilder.CLEAN_BUILD;
+ currentLastBuiltTree = currentBuilder.getLastBuiltTree();
+ // If no tree is available we have to do a full build
+ if (!clean && currentLastBuiltTree == null)
+ trigger = IncrementalProjectBuilder.FULL_BUILD;
+ //don't build if this builder doesn't respond to the given trigger
+ if (!builder.getCommand().isBuilding(trigger)) {
+ 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;
+ 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;
+ }
+ 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);
+ //release workspace lock while calling builders
+ depth = getWorkManager().beginUnprotected();
+ //do the build
+ SafeRunner.run(getSafeRunnable(trigger, args, status, monitor));
+ } finally {
+ if (depth >= 0)
+ getWorkManager().endUnprotected(depth);
+ // Be sure to clean up after ourselves.
+ if (clean || currentBuilder.wasForgetStateRequested()) {
+ currentBuilder.setLastBuiltTree(null);
+ } 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(IProject project, int trigger, 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(project, command, i, status);
+ 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.
+ * @return A status indicating if the build succeeded or failed
+ */
+ private IStatus basicBuild(IProject project, int trigger, IProgressMonitor monitor) {
+ if (!canRun(trigger))
+ return Status.OK_STATUS;
+ try {
+ hookStartBuild(trigger);
+ MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.events_errors, null);
+ basicBuild(project, trigger, status, monitor);
+ return status;
+ } finally {
+ hookEndBuild(trigger);
+ }
+ }
+
+ private void basicBuild(final IProject project, final int trigger, final MultiStatus status, final IProgressMonitor monitor) {
+ try {
+ 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() {
+ 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));
+ }
+
+ public void run() throws Exception {
+ basicBuild(project, trigger, commands, status, monitor);
+ }
+ };
+ SafeRunner.run(code);
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * Runs the builder with the given name on the given project.
+ * @return A status indicating if the build succeeded or failed
+ */
+ private IStatus basicBuild(IProject project, int trigger, String builderName, Map args, IProgressMonitor monitor) {
+ 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(trigger);
+ MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.events_errors, null);
+ ICommand command = getCommand(project, builderName, args);
+ try {
+ IncrementalProjectBuilder builder = getBuilder(project, command, -1, status);
+ 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(IProject[] ordered, IProject[] unordered, int trigger, MultiStatus status, IProgressMonitor monitor) {
+ int projectWork = ordered.length + unordered.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 < ordered.length; i++) {
+ if (ordered[i].isAccessible()) {
+ basicBuild(ordered[i], trigger, status, Policy.subMonitorFor(monitor, projectWork));
+ builtProjects.add(ordered[i]);
+ }
+ }
+ for (int i = 0; i < unordered.length; i++) {
+ if (unordered[i].isAccessible()) {
+ basicBuild(unordered[i], trigger, status, Policy.subMonitorFor(monitor, projectWork));
+ builtProjects.add(unordered[i]);
+ }
+ }
+ //subsequent builds should always be incremental
+ trigger = IncrementalProjectBuilder.INCREMENTAL_BUILD;
+ }
+ }
+
+ /**
+ * Runs all builders on all projects.
+ * @return A status indicating if the build succeeded or failed
+ */
+ public IStatus build(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(trigger);
+ IProject[] ordered = workspace.getBuildOrder();
+ HashSet leftover = new HashSet(Arrays.asList(workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN)));
+ leftover.removeAll(Arrays.asList(ordered));
+ IProject[] unordered = (IProject[]) leftover.toArray(new IProject[leftover.size()]);
+ MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.BUILD_FAILED, Messages.events_errors, null);
+ basicBuildLoop(ordered, unordered, 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.
+ * @return A status indicating if the build succeeded or failed
+ */
+ public IStatus build(IProject project, int trigger, String builderName, Map args, IProgressMonitor monitor) {
+ monitor = Policy.monitorFor(monitor);
+ if (builderName == null)
+ return basicBuild(project, trigger, monitor);
+ return basicBuild(project, 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 that are
+ * in the builder spec, and that have a last built state, even if they
+ * have not been instantiated this session.
+ */
+ public ArrayList createBuildersPersistentInfo(IProject project) throws CoreException {
+ /* get the old builders (those not yet instantiated) */
+ ArrayList oldInfos = getBuildersPersistentInfo(project);
+
+ ICommand[] commands = ((Project) project).internalGetDescription().getBuildSpec(false);
+ if (commands.length == 0)
+ return null;
+
+ /* build the new list */
+ ArrayList newInfos = new ArrayList(commands.length);
+ for (int i = 0; i < commands.length; i++) {
+ String builderName = commands[i].getBuilderName();
+ BuilderPersistentInfo info = null;
+ IncrementalProjectBuilder builder = ((BuildCommand) commands[i]).getBuilder();
+ if (builder == null) {
+ // if the builder was not instantiated, use the old info if any.
+ if (oldInfos != null)
+ info = getBuilderInfo(oldInfos, builderName, 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(), builderName, i);
+ info.setLastBuildTree(oldTree);
+ info.setInterestingProjects(((InternalBuilder) builder).getInterestingProjects());
+ }
+ }
+ if (info != null)
+ newInfos.add(info);
+ }
+ return newInfos;
+ }
+
+ private String debugBuilder() {
+ return currentBuilder == null ? "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 project The project 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(IProject project, ICommand command, int buildSpecIndex, MultiStatus status) throws CoreException {
+ InternalBuilder result = ((BuildCommand) command).getBuilder();
+ if (result == null) {
+ result = initializeBuilder(command.getBuilderName(), project, buildSpecIndex, status);
+ ((BuildCommand) command).setBuilder((IncrementalProjectBuilder) result);
+ result.setCommand(command);
+ result.setProject(project);
+ result.startupOnInitialize();
+ }
+ 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;
+ }
+
+ /**
+ * Removes the builder persistent info from the map corresponding to the
+ * given builder name and build spec index, or null
if not found
+ *
+ * @param buildSpecIndex The index in the build spec, or -1 if unknown
+ */
+ private BuilderPersistentInfo getBuilderInfo(ArrayList infos, String builderName, int buildSpecIndex) {
+ //try to match on builder index, but if not match is found, use the name alone
+ //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 = (BuilderPersistentInfo) it.next();
+ //match on name and build spec index if known
+ if (info.getBuilderName().equals(builderName)) {
+ //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.
+ */
+ 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;
+ }
+
+ 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"); //$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() {
+ 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.WARNING, pluginId, IResourceStatus.BUILD_FAILED, message, null));
+
+ //add the exception status to the MultiStatus
+ if (e instanceof CoreException)
+ status.add(((CoreException) e).getStatus());
+ else {
+ message = e.getMessage();
+ if (message == null)
+ message = NLS.bind(Messages.events_unknown, e.getClass().getName(), builderName);
+ status.add(new Status(IStatus.WARNING, pluginId, IResourceStatus.BUILD_FAILED, message, e));
+ }
+ }
+
+ 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((IProject[]) 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;
+ }
+
+ 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 the given project has 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();
+ }
+
+ /**
+ * 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(int trigger) {
+ building = true;
+ if (Policy.DEBUG_BUILD_STACK) {
+ IStatus info = new Status(IStatus.INFO, ResourcesPlugin.PI_RESOURCES, 1, "Starting build: " + debugTrigger(trigger), new RuntimeException().fillInStackTrace()); //$NON-NLS-1$
+ Policy.log(info);
+ }
+ }
+
+ /**
+ * 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, IProject project, int buildSpecIndex, MultiStatus status) throws CoreException {
+ 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, 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 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 projects that
+ * have changed since the last build, and the set of projects 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 project. 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, ArrayList 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$
+ }
+ }
+
+ public void shutdown(IProgressMonitor monitor) {
+ autoBuildJob.cancel();
+ }
+
+ 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);
+ return name + "(" + builder.getProject().getName() + ")"; //$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);
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuilderPersistentInfo.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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.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;
+
+ public BuilderPersistentInfo(String projectName, String builderName, int buildSpecIndex) {
+ this.projectName = projectName;
+ this.builderName = builderName;
+ this.buildSpecIndex = buildSpecIndex;
+ }
+ public String getBuilderName() {
+ return builderName;
+ }
+
+ public int getBuildSpecIndex() {
+ return buildSpecIndex;
+ }
+
+ public IProject[] getInterestingProjects() {
+ return interestingProjects;
+ }
+
+ public ElementTree getLastBuiltTree() {
+ return lastBuildTree;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public void setInterestingProjects(IProject[] projects) {
+ interestingProjects = projects;
+ }
+
+ public void setLastBuildTree(ElementTree tree) {
+ lastBuildTree = tree;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ILifecycleListener.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ILifecycleListener.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,197 @@
+/*******************************************************************************
+ * 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.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 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;
+ private IProject project;
+
+ /**
+ * 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 request to forget last built states.
+ */
+ final void clearForgetLastBuiltState() {
+ forgetStateRequested = false;
+ }
+
+ /*
+ * @see IncrementalProjectBuilder#forgetLastBuiltState
+ */
+ protected void forgetLastBuiltState() {
+ oldState = null;
+ forgetStateRequested = true;
+ }
+
+ /*
+ * @see IncrementalProjectBuilder#getCommand
+ */
+ protected ICommand getCommand() {
+ return (ICommand)((BuildCommand)command).clone();
+ }
+
+ /*
+ * @see IncrementalProjectBuilder#forgetLastBuiltState
+ */
+ protected IResourceDelta getDelta(IProject aProject) {
+ return buildManager.getDelta(aProject);
+ }
+
+ 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 project;
+ }
+
+ /*
+ * @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 project for which this builder operates.
+ * @see #getProject()
+ */
+ final void setProject(IProject value) {
+ Assert.isTrue(project == null);
+ project = value;
+ }
+
+ /*
+ * @see IncrementalProjectBuilder#startupOnInitialize
+ */
+ protected abstract void startupOnInitialize();
+
+ /**
+ * Returns true if the builder requested that its last built state be
+ * forgetten, and false otherwise.
+ */
+ final boolean wasForgetStateRequested() {
+ return forgetStateRequested;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/LifecycleEvent.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/LifecycleEvent.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * 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.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 PRE_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_PROJECT_REFRESH = 0x1000;
+
+ /**
+ * 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NodeIDMap.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NodeIDMap.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/NotificationManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,328 @@
+/*******************************************************************************
+ * 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 java.util.*;
+import org.eclipse.core.internal.resources.IManager;
+import org.eclipse.core.internal.resources.Workspace;
+import org.eclipse.core.internal.utils.Messages;
+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() {
+ public void run(IProgressMonitor monitor) {
+ }
+ };
+
+ public NotifyJob() {
+ super(Messages.resources_updating);
+ setSystem(true);
+ }
+
+ 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 Set avoidNotify = 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();
+ }
+
+ 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_PROJECT_REFRESH :
+ if (!listeners.hasListenerFor(IResourceChangeEvent.PRE_REFRESH))
+ return;
+ project = (IProject) event.resource;
+ notify(getListeners(), new ResourceChangeEvent(workspace, IResourceChangeEvent.PRE_REFRESH, project), true);
+ break;
+ }
+ }
+
+ private void notify(ResourceChangeListenerList.ListenerEntry[] resourceListeners, final IResourceChangeEvent 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() {
+ public void handleException(Throwable e) {
+ // exception logged in SafeRunner#run
+ }
+
+ public void run() throws Exception {
+ 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;
+ }
+
+ public void shutdown(IProgressMonitor monitor) {
+ //wipe out any existing listeners
+ listeners = new ResourceChangeListenerList();
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/PathVariableChangeEvent.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * 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 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()
+ */
+ public IPath getValue() {
+ return value;
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IPathVariableChangeEvent#getVariableName()
+ */
+ public String getVariableName() {
+ return variableName;
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IPathVariableChangeEvent#getType()
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Return a string representation of this object.
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeEvent.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * 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 java.util.*;
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.resources.*;
+
+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)
+ */
+ 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 = (MarkerSet) 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 (IMarkerDelta[]) matching.toArray(new IMarkerDelta[matching.size()]);
+ }
+
+ /**
+ * @see IResourceChangeEvent#getBuildKind()
+ */
+ public int getBuildKind() {
+ return trigger;
+ }
+
+ /**
+ * @see IResourceChangeEvent#getDelta()
+ */
+ public IResourceDelta getDelta() {
+ return delta;
+ }
+
+ /**
+ * @see IResourceChangeEvent#getResource()
+ */
+ public IResource getResource() {
+ return resource;
+ }
+
+ /**
+ * @see IResourceChangeEvent#getType()
+ */
+ public int getType() {
+ return type;
+ }
+
+ public void setDelta(IResourceDelta value) {
+ delta = value;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/events/ResourceChangeListenerList.java Mon Jun 01 10:16:20 2009 -0500
@@ -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.
+ * add
method checks for and eliminates
+ * duplicates based on identity (not equality). Likewise, the
+ * remove
method compares based on identity.
+ * 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BlobStore.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * 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 java.io.InputStream;
+import java.util.Iterator;
+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 (Iterator i = set.iterator(); i.hasNext();)
+ deleteBlob((UniversalUniqueIdentifier) i.next());
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,386 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.*;
+import java.util.*;
+import org.eclipse.core.internal.resources.ResourceException;
+import org.eclipse.core.internal.resources.ResourceStatus;
+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 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).
+ */
+ public void afterSaving(Bucket bucket) throws CoreException {
+ // empty implementation, subclasses to override
+ }
+
+ 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[][]),
+ * 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 = (Map.Entry) i.next();
+ IPath path = new Path((String) 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 = (Map.Entry) i.next();
+ writeEntryKey(destination, (String) entry.getKey());
+ writeEntryValue(destination, entry.getValue());
+ }
+ } finally {
+ destination.close();
+ }
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/BucketTree.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.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.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;
+ boolean failed = false;
+ try {
+ stream = new FileOutputStream(versionFile);
+ stream.write(current.getVersion());
+ } catch (IOException e) {
+ failed = true;
+ String message = NLS.bind(Messages.resources_writeWorkspaceMeta, versionFile.getAbsolutePath());
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, null, message, e);
+ } finally {
+ try {
+ if (stream != null)
+ stream.close();
+ } catch (IOException e) {
+ if (!failed) {
+ String message = NLS.bind(Messages.resources_writeWorkspaceMeta, versionFile.getAbsolutePath());
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, null, message, e);
+ }
+ }
+ }
+ }
+
+ private char[] translateSegment(String segment) {
+ // String.hashCode algorithm is API
+ return HEX_STRINGS[Math.abs(segment.hashCode()) % SEGMENT_QUOTA];
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CollectSyncStatusVisitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * 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.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;
+ }
+
+ protected void createResource(UnifiedTreeNode node, Resource target) {
+ changed(target);
+ }
+
+ protected void deleteResource(UnifiedTreeNode node, Resource target) {
+ if (!ignoreLocalDeletions)
+ changed(target);
+ }
+
+ protected void fileToFolder(UnifiedTreeNode node, Resource target) {
+ changed(target);
+ }
+
+ 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;
+ }
+
+ protected void makeLocal(UnifiedTreeNode node, Resource target) {
+ changed(target);
+ }
+
+ protected void refresh(Container parent) {
+ changed(parent);
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/CopyVisitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,185 @@
+/*******************************************************************************
+ * 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.localstore;
+
+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 (!isDeep && source.isLinked()) {
+ destination.createLink(source.getRawLocationURI(), updateFlags & IResource.ALLOW_MISSING_LOCAL, null);
+ return false;
+ }
+ 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);
+ } 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) {
+ /* 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);
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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
+ * Matt McCutchen - fix for bug 174492
+ *******************************************************************************/
+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 (int i = 0; i < skipList.size(); i++) {
+ IResource target = (IResource) skipList.get(i);
+ 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() || node.isSymbolicLink())
+ return;
+ if (node.isFolder()) {
+ monitor.subTask(NLS.bind(Messages.localstore_deleting, target.getFullPath()));
+ for (Iterator children = node.getChildren(); children.hasNext();)
+ recursiveKeepHistory(store, (UnifiedTreeNode) 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, (IResource) skipList.get(i)))
+ return true;
+ return false;
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileStoreRoot.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.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.IPathVariableManager;
+import org.eclipse.core.resources.ResourcesPlugin;
+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 = null;
+
+ private URI root;
+
+ private final IPathVariableManager variableManager;
+
+ /**
+ * 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.variableManager = ResourcesPlugin.getWorkspace().getPathVariableManager();
+ this.root = rootURI;
+ this.chop = workspacePath.segmentCount();
+ this.localRoot = toLocalPath(root);
+ }
+
+ /**
+ * Returns the resolved, absolute file system location of the resource
+ * corresponding to the given workspace path, or null if none could
+ * be computed.
+ */
+ public URI computeURI(IPath workspacePath) {
+ IPath childPath = workspacePath.removeFirstSegments(chop);
+ final URI rootURI = variableManager.resolveURI(root);
+ if (childPath.segmentCount() == 0)
+ return rootURI;
+ try {
+ return EFS.getStore(rootURI).getChild(childPath).toURI();
+ } catch (CoreException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Creates an IFileStore for a given workspace path.
+ * @exception CoreException If the file system for that resource is undefined
+ */
+ IFileStore createStore(IPath workspacePath) throws CoreException {
+ IPath childPath = workspacePath.removeFirstSegments(chop);
+ IFileStore rootStore;
+ final URI uri = variableManager.resolveURI(root);
+ 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.getChild(childPath);
+ }
+
+ boolean isValid() {
+ return isValid;
+ }
+
+ IPath localLocation(IPath workspacePath) {
+ if (localRoot == null)
+ return null;
+ IPath location;
+ if (workspacePath.segmentCount() <= chop)
+ location = localRoot;
+ else
+ location = localRoot.append(workspacePath.removeFirstSegments(chop));
+ location = variableManager.resolvePath(location);
+ //if path is still relative then path variable could not be resolved
+ if (!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);
+ }
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,1007 @@
+/*******************************************************************************
+ * 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
+ * Martin Oberhuber (Wind River) - [210664] descriptionChanged(): ignore LF style
+ *******************************************************************************/
+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.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.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 {
+
+ /**
+ * The history store is initialized lazily - always use the accessor method
+ */
+ protected IHistoryStore _historyStore;
+ protected Workspace workspace;
+
+ 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.
+ * 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.
+ * @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.
+ */
+ public IResource[] allResourcesFor(URI location, boolean files) {
+ 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);
+ 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.
+ */
+ public IContainer containerForLocation(IPath location) {
+ IPath path = pathForLocation(location);
+ return path == null ? null : (IContainer) resourceFor(path, false);
+ }
+
+ 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) {
+ boolean newGotLine = (newChar == '\r' || newChar == '\n');
+ boolean oldGotLine = (oldChar == '\r' || oldChar == '\n');
+ if (newGotLine || oldGotLine) {
+ //Ignore newlines if in both streams
+ if (newGotLine != oldGotLine)
+ return true;
+ while (newChar == '\r' || newChar == '\n')
+ newChar = newStream.read();
+ while (oldChar == '\r' || oldChar == '\n')
+ oldChar = oldStream.read();
+ }
+ //Now we are pointing to a char that's not a newline.
+ if (newChar != oldChar)
+ return true;
+ newChar = newStream.read();
+ oldChar = oldStream.read();
+ }
+ //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
+ */
+ 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;
+ }
+
+ /**
+ * 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.
+ */
+ public IFile fileForLocation(IPath location) {
+ IPath path = pathForLocation(location);
+ return path == null ? null : (IFile) resourceFor(path, true);
+ }
+
+ /**
+ * @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();
+ _historyStore = ResourcesCompatibilityHelper.createHistoryStore(location, 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());
+ } 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_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());
+ }
+
+ /**
+ * 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();
+ try {
+ new ModelObjectWriter().write(description, out);
+ } 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
+ IFile descriptionFile = target.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
+ 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();
+ }
+
+ /* (non-Javadoc)
+ * Returns true if the given resource is synchronized with the file system
+ * to the given depth. Returns false otherwise.
+ *
+ * @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.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) {
+ //visitor throws an exception if out of sync
+ return false;
+ }
+ return true;
+ }
+
+ 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.
+ */
+ public IPath locationFor(IResource target) {
+ return getStoreRoot(target).localLocation(target.getFullPath());
+ }
+
+ /**
+ * Returns the resolved, absolute file system location of the given resource.
+ * Returns null if the location could not be resolved.
+ */
+ public URI locationURIFor(IResource target) {
+ return getStoreRoot(target).computeURI(target.getFullPath());
+ }
+
+ 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);
+ }
+
+ /**
+ * Returns a resource path to the given local location. Returns null if
+ * it is not under a project's location.
+ */
+ protected IPath pathForLocation(IPath location) {
+ if (workspace.getRoot().getLocation().equals(location))
+ return Path.ROOT;
+ 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();
+ return project.getFullPath().append(location.removeFirstSegments(segmentsToRemove));
+ }
+ }
+ return null;
+ }
+
+ public InputStream read(IFile target, boolean force, IProgressMonitor monitor) throws CoreException {
+ IFileStore store = getStore(target);
+ if (!force) {
+ final IFileInfo fileInfo = store.fetchInfo();
+ if (!fileInfo.exists()) {
+ 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()) {
+ 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, null));
+ description = new ProjectDescriptionReader(target).read(new InputSource(in));
+ } 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) {
+ //don't trust the project name in the description file
+ description.setName(target.getName());
+ if (!isDefaultLocation)
+ description.setLocationURI(projectLocation);
+ if (creation && privateDescription != null)
+ description.setDynamicReferences(privateDescription.getDynamicReferences(false));
+ }
+ 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) {
+ 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);
+ }
+
+ public void shutdown(IProgressMonitor monitor) throws CoreException {
+ if (_historyStore != null)
+ _historyStore.shutdown(monitor);
+ }
+
+ public void startup(IProgressMonitor monitor) throws CoreException {
+ //nothing to do
+ }
+
+ /**
+ * 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)) {
+ // test if timestamp is the same since last synchronization
+ ResourceInfo info = ((Resource) target).getResourceInfo(true, false);
+ if (lastModified != info.getLocalSyncInfo()) {
+ String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, target.getFullPath());
+ throw new ResourceException(IResourceStatus.OUT_OF_SYNC_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);
+ //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);
+ new ModelObjectWriter().write(desc, out);
+ } catch (IOException e) {
+ String msg = NLS.bind(Messages.resources_writeMeta, target.getFullPath());
+ throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, target.getFullPath(), msg, e);
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // ignore failure to close stream
+ }
+ }
+ }
+ //for backwards compatibility, ensure the old .prj file is deleted
+ getWorkspace().getMetaArea().clearOldDescription(target);
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/HistoryBucket.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,316 @@
+/*******************************************************************************
+ * 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.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.
+ *
+ * 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]
+ *
+ *
+ * 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]
+ *
+ *
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();
+ long length = info.getLength();
+ boolean result = length <= description.getMaxFileStateSize();
+ if (Policy.DEBUG_HISTORY && !result)
+ System.out.println("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);
+ }
+
+ public synchronized void remove(IPath root, IProgressMonitor monitor) {
+ try {
+ final Set tmpBlobsToRemove = blobsToRemove;
+ tree.accept(new Bucket.Visitor() {
+ 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()
+ */
+ public synchronized void removeGarbage() {
+ try {
+ final Set tmpBlobsToRemove = blobsToRemove;
+ tree.accept(new Bucket.Visitor() {
+ 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);
+ }
+ }
+
+ public synchronized void shutdown(IProgressMonitor monitor) throws CoreException {
+ tree.close();
+ }
+
+ public void startup(IProgressMonitor monitor) {
+ // nothing to be done
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IHistoryStore.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.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 ornull
+ *
+ * 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 ofDEPTH_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, ornull
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 + * @returntrue
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, ornull
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, ornull
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/ILocalStoreConstants.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/ILocalStoreConstants.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IUnifiedTreeVisitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IUnifiedTreeVisitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/IsSynchronizedVisitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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.internal.resources.Resource;
+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. Nodes
+ * discovered to be out of sync are not brought to be in sync with the workspace.
+ */
+public class IsSynchronizedVisitor extends CollectSyncStatusVisitor {
+ static class ResourceChangedException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+ }
+
+ protected static ResourceChangedException exception = new ResourceChangedException();
+
+ /**
+ * Creates a new IsSynchronizedVisitor.
+ */
+ public IsSynchronizedVisitor(IProgressMonitor monitor) {
+ super("", monitor); //$NON-NLS-1$
+ }
+
+ /**
+ * @see CollectSyncStatusVisitor#changed(Resource)
+ */
+ protected void changed(Resource target) {
+ throw exception;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/PrefixPool.java Mon Jun 01 10:16:20 2009 -0500
@@ -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: + *
+ * 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.
+ *
true
if 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalAliasVisitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * 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.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);
+ }
+
+ 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++)
+ super.createResource(node, (Resource) aliases[i]);
+ }
+
+ 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)
+ for (int i = 0; i < aliases.length; i++)
+ super.deleteResource(node, (Resource) aliases[i]);
+ }
+
+ 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++)
+ super.resourceChanged(node, (Resource) aliases[i]);
+ }
+
+ 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]);
+ }
+
+ 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]);
+ }
+
+ protected void refresh(Container parent) throws CoreException {
+ parent.getLocalManager().refresh(parent, IResource.DEPTH_ZERO, true, null);
+ }
+
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/RefreshLocalVisitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,328 @@
+/*******************************************************************************
+ * 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.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 */
+ if (node.getLevel() == 0) {
+ 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();
+ 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()) {
+ //non-local files are always in sync
+ if (target.isLocal(IResource.DEPTH_ZERO)) {
+ 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;
+ }
+
+ public boolean visit(UnifiedTreeNode node) throws CoreException {
+ Policy.checkCanceled(monitor);
+ try {
+ 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 (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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,181 @@
+/*******************************************************************************
+ * 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 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;
+ }
+
+ public int available() throws IOException {
+ 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;
+ }
+ }
+ }
+
+ 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();
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyOutputStream.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyOutputStream.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * 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 java.io.*;
+
+/**
+ * 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();
+ } finally {
+ isOpen = false;
+ close();
+ }
+ }
+
+ public void write(int b) throws IOException {
+ if (!isOpen)
+ open();
+ super.write(b);
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileInputStream.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * 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.*;
+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.
+ * The user has the option to define the temporary location or
+ * it will be created in the default-temporary directory
+ * (see java.io.File for details).
+ */
+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$
+
+ public SafeFileOutputStream(File file) throws IOException {
+ this(file.getAbsolutePath(), null);
+ }
+
+ /**
+ * If targetPath is null, the file will be created in the default-temporary directory.
+ */
+ 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));
+ }
+
+ 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);
+ } finally {
+ FileUtil.safeClose(source);
+ FileUtil.safeClose(destination);
+ }
+ }
+
+ protected void createTempFile(String tempPath) {
+ if (tempPath == null)
+ tempPath = target.getAbsolutePath() + EXTENSION;
+ temp = new File(tempPath);
+ }
+
+ 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);
+ }
+ }
+
+ public void write(int b) throws IOException {
+ try {
+ output.write(b);
+ } catch (IOException e) {
+ failed = true;
+ throw e; // rethrow
+ }
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTree.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,551 @@
+/*******************************************************************************
+ * 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
+ * Martin Oberhuber (Wind River) - [105554] handle cyclic symbolic links
+ * Martin Oberhuber (Wind River) - [232426] shared prefix histories for symlinks
+ *******************************************************************************/
+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);
+ }
+
+ public void accept(IUnifiedTreeVisitor visitor, int depth) throws CoreException {
+ Assert.isNotNull(root);
+ initializeQueue();
+ setLevel(0, depth);
+ while (!queue.isEmpty()) {
+ UnifiedTreeNode node = (UnifiedTreeNode) 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
+ freeNodes.add(node);
+ }
+ }
+
+ 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 = 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 = (UnifiedTreeNode) queue.peek();
+ if (isChildrenMarker(nextNode))
+ queue.remove();
+ nextNode = (UnifiedTreeNode) 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, false);
+ }
+
+ /**
+ * 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 = (UnifiedTreeNode) 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 = (UnifiedTreeNode) 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 = fileTree != null ? fileTree.getChildInfos(store) : store.childInfos(EFS.NONE, null);
+ if (list == null)
+ return NO_CHILDREN;
+ 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 relative path that just points backward
+ public static Pattern trivialSymlinkPattern = Pattern.compile("\\.[./]*"); //$NON-NLS-1$
+ }
+
+ /**
+ * 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 + * @returntrue
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.trivialSymlinkPattern.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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/UnifiedTreeNode.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.internal.localstore;
+
+import java.util.Iterator;
+import org.eclipse.core.filesystem.*;
+import org.eclipse.core.internal.resources.Resource;
+import org.eclipse.core.resources.IResource;
+
+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();
+ }
+
+ public boolean existsInWorkspace() {
+ return existsWorkspace;
+ }
+
+ /**
+ * Returns an Enumeration of UnifiedResourceNode.
+ */
+ 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;
+ }
+
+ public void setExistsWorkspace(boolean exists) {
+ this.existsWorkspace = exists;
+ }
+
+ protected void setFirstChild(UnifiedTreeNode child) {
+ this.child = child;
+ }
+
+ public void setResource(IResource resource) {
+ this.resource = resource;
+ }
+
+ public String toString() {
+ String s = resource == null ? "null" : resource.getFullPath().toString(); //$NON-NLS-1$
+ return "Node: " + s; //$NON-NLS-1$
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/IPropertyManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.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"
).
+ *
+ * The qualifier part of the property name must be the unique identifier
+ * of the declaring plug-in (e.g. "com.example.plugin"
).
+ *
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;
+ }
+
+ 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 Object getPropertyName(int i) {
+ return new QualifiedName(this.value[i][0], this.value[i][1]);
+ }
+
+ public Object getPropertyValue(int i) {
+ return this.value[i][2];
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ 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(); + } + + 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() + */ + 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); + } + + protected byte getVersion() { + return VERSION; + } + + /* (non-Javadoc) + * @see org.eclipse.core.internal.localstore.Bucket#getVersionFileName() + */ + protected String getVersionFileName() { + return "properties.version"; //$NON-NLS-1$ + } + + public void load(String newProjectName, File baseLocation, boolean force) throws CoreException { + qualifierIndex.clear(); + super.load(newProjectName, baseLocation, force); + } + + 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] = (String) 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; + } + + 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); + } + + 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/properties/PropertyManager2.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,175 @@ +/******************************************************************************* + * 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.internal.properties; + +import java.io.File; +import java.util.*; +import org.eclipse.core.internal.localstore.Bucket; +import org.eclipse.core.internal.localstore.BucketTree; +import org.eclipse.core.internal.localstore.Bucket.Entry; +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 { + 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; + } + + 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 = (PropertyEntry) i.next(); + tree.loadBucketFor(entry.getPath()); + bucket.setProperties(entry); + while (i.hasNext()) + bucket.setProperties((PropertyEntry) i.next()); + bucket.save(); + } + + 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()); + } + + 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(); + } + + 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); + } + + public synchronized void deleteProperties(IResource target, int depth) throws CoreException { + tree.accept(new PropertyBucket.Visitor() { + public int visit(Entry entry) { + entry.delete(); + return CONTINUE; + } + }, target.getFullPath(), depth == IResource.DEPTH_INFINITE ? BucketTree.DEPTH_INFINITE : depth); + } + + public void deleteResource(IResource target) throws CoreException { + deleteProperties(target, IResource.DEPTH_INFINITE); + } + + public synchronized Map getProperties(IResource target) throws CoreException { + final Map result = new HashMap(); + tree.accept(new PropertyBucket.Visitor() { + 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; + } + + 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(); + } + + 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() > 2 * 1024) { + String message = NLS.bind(Messages.properties_valueTooLong, name.getQualifier(), name.getLocalName()); + 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(); + } + + public void shutdown(IProgressMonitor monitor) throws CoreException { + tree.close(); + } + + public void startup(IProgressMonitor monitor) { + // nothing to do + } +} diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/FilePropertyTester.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2005, 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.propertytester; + +import org.eclipse.core.internal.utils.Policy; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.content.IContentDescription; +import org.eclipse.core.runtime.content.IContentType; + +/** + * A property tester for various properties of files. + * + * @since 3.2 + */ +public class FilePropertyTester extends ResourcePropertyTester { + + /** + * A property indicating that we are looking to verify that the file matches + * the content type matching the given identifier. The identifier is + * provided as the expected value. + */ + private static final String CONTENT_TYPE_ID = "contentTypeId"; //$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) + */ + 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)); + return false; + } + + /** + * Tests whether the content type for
file
matches the
+ * 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 for which the content type should be determined; must
+ * not be null
.
+ * @param contentTypeId
+ * The expected content type; must not be null
.
+ * @return true
iff the best matching content type has an
+ * identifier that matches contentTypeId
;
+ * false
otherwise.
+ */
+ private boolean testContentType(final IFile file, String contentTypeId) {
+ final String expectedValue = contentTypeId.trim();
+
+ String actualValue = null;
+ try {
+ IContentDescription contentDescription = file.getContentDescription();
+ if (contentDescription != null) {
+ IContentType contentType = contentDescription.getContentType();
+ actualValue = contentType.getId();
+ }
+ } catch (CoreException e) {
+ Policy.log(IStatus.ERROR, "Core exception while retrieving the content description", e);//$NON-NLS-1$
+ }
+ return expectedValue.equals(actualValue);
+ }
+
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ProjectPropertyTester.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 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.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)
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourceMappingPropertyTester.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.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 {
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ResourcePropertyTester.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,228 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.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$
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.expressions.IPropertyTester#test(java.lang.Object,
+ * java.lang.String, java.lang.Object[], java.lang.Object)
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/StringMatcher.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,230 @@
+/*******************************************************************************
+ * 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.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 = (String[]) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/InternalRefreshProvider.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/MonitorManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,349 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 - Initial API and implementation
+ *******************************************************************************/
+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.
+ */
+ 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 = (RefreshProvider[]) 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;
+ }
+
+ 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 = (List) 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 (RefreshManager.DEBUG)
+ System.err.println(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 = (List) 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 = (IResource) 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)
+ */
+ 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 = ((List) i.next()).iterator(); j.hasNext();) {
+ IResource resource = (IResource) 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 = (IResource) i.next();
+ unmonitor(resource);
+ monitor(resource);
+ }
+ }
+ }
+
+ private void registerMonitor(IRefreshMonitor monitor, IResource resource) {
+ // synchronized: protect the collection during add
+ synchronized (registeredMonitors) {
+ List resources = (List) registeredMonitors.get(monitor);
+ if (resources == null) {
+ resources = new ArrayList(1);
+ registeredMonitors.put(monitor, resources);
+ }
+ if (!resources.contains(resource))
+ resources.add(resource);
+ }
+ if (RefreshManager.DEBUG)
+ System.out.println(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 = (List) registeredMonitors.get(monitor);
+ if (resources != null && !resources.isEmpty())
+ resources.remove(resource);
+ else
+ registeredMonitors.remove(monitor);
+ }
+ if (RefreshManager.DEBUG)
+ System.out.println(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((IResource) 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 (RefreshManager.DEBUG)
+ System.out.println(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 = (IRefreshMonitor) i.next();
+ monitor.unmonitor(null);
+ }
+ }
+ registeredMonitors.clear();
+ if (RefreshManager.DEBUG)
+ System.out.println(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();) {
+ Map.Entry current = (Map.Entry) i.next();
+ List resources = (List) current.getValue();
+ if ((resources != null) && !resources.isEmpty() && resources.contains(resource)) {
+ ((IRefreshMonitor) 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]);
+ }
+
+ 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
+ }
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/PollingMonitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,215 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 - Initial API and implementation
+ *******************************************************************************/
+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.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.
+ */
+ 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 (RefreshManager.DEBUG)
+ System.out.println(RefreshManager.DEBUG_PREFIX + "New polling iteration on " + toRefresh.size() + " roots"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ final int oldSize = toRefresh.size();
+ if (RefreshManager.DEBUG)
+ System.out.println(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((IResource) 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 (RefreshManager.DEBUG)
+ System.out.println(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 (RefreshManager.DEBUG)
+ System.out.println(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 (RefreshManager.DEBUG)
+ System.out.println(RefreshManager.DEBUG_PREFIX + "new hot root: " + resource); //$NON-NLS-1$
+ }
+
+ /* (non-Javadoc)
+ * @see Job#shouldRun
+ */
+ 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)
+ */
+ public synchronized void unmonitor(IResource resource) {
+ if (resource == null)
+ resourceRoots.clear();
+ else
+ resourceRoots.remove(resource);
+ if (resourceRoots.isEmpty())
+ cancel();
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshJob.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 - Initial API and implementation
+ *******************************************************************************/
+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 = ((IResource) 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)
+ */
+ 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 (IResource) 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
+ */
+ 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 (RefreshManager.DEBUG)
+ 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, null);
+ 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 (RefreshManager.DEBUG)
+ System.out.println(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()
+ */
+ public synchronized boolean shouldRun() {
+ return !fRequests.isEmpty();
+ }
+
+ /**
+ * Starts the refresh job
+ */
+ public void start() {
+ if (RefreshManager.DEBUG)
+ System.out.println(RefreshManager.DEBUG_PREFIX + " enabling auto-refresh"); //$NON-NLS-1$
+ }
+
+ /**
+ * Stops the refresh job
+ */
+ public void stop() {
+ if (RefreshManager.DEBUG)
+ System.out.println(RefreshManager.DEBUG_PREFIX + " disabling auto-refresh"); //$NON-NLS-1$
+ cancel();
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/refresh/RefreshManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * 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
+ *
+ * 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.Policy;
+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 boolean DEBUG = Policy.DEBUG_AUTO_REFRESH;
+ 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();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.refresh.IRefreshResult#monitorFailed(org.eclipse.core.resources.refresh.IRefreshMonitor, org.eclipse.core.resources.IResource)
+ */
+ public void monitorFailed(IRefreshMonitor monitor, IResource resource) {
+ monitors.monitorFailed(monitor, resource);
+ }
+
+ /**
+ * Checks for changes to the PREF_AUTO_UPDATE property.
+ * @see Preferences.IPropertyChangeListener#propertyChange(Preferences.PropertyChangeEvent)
+ */
+ 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);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.refresh.IRefreshResult#refresh(org.eclipse.core.resources.IResource)
+ */
+ 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.
+ */
+ 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.
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/AliasManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,706 @@
+/*******************************************************************************
+ * 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
+ * manklu@web.de - fix for bug 156082
+ * Bert Vingerhoets - fix for bug 169975
+ *******************************************************************************/
+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.Messages;
+import org.eclipse.core.internal.utils.Policy;
+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;
+
+ 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;
+
+ 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;
+ }
+ 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) {
+ Iterator duplicates = ((List) value).iterator();
+ while (duplicates.hasNext())
+ doit.doit((IResource) 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) {
+ Iterator duplicates = ((List) value).iterator();
+ while (duplicates.hasNext())
+ doit.doit((IResource) 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 = (Map.Entry) entries.next();
+ //value is either single resource or List of resources
+ IFileStore currentStore = (IFileStore) current.getKey();
+ IResource currentResource = null;
+ Object value = current.getValue();
+ if (value instanceof List) {
+ //if there are several then they're all overlapping
+ Iterator duplicates = ((List) value).iterator();
+ while (duplicates.hasNext())
+ doit.doit(((IResource) 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;
+ }
+ 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 (Iterator it = links.values().iterator(); it.hasNext();) {
+ LinkDescription linkDesc = (LinkDescription) it.next();
+ IResource link = project.findMember(linkDesc.getProjectRelativePath());
+ if (link != null) {
+ try {
+ addToLocationsMap(link, EFS.getStore(linkDesc.getLocationURI()));
+ } catch (CoreException e) {
+ //ignore links with invalid locations
+ }
+ }
+ }
+ }
+
+ private void addToLocationsMap(IResource link, IFileStore location) {
+ if (location != null)
+ 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 (IResource[]) aliases.toArray(new IResource[size]);
+ }
+
+ /**
+ * 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").
+ */
+ private Comparator getComparator() {
+ return new Comparator() {
+ public int compare(Object o1, Object o2) {
+ IFileStore store1 = (IFileStore) o1;
+ IFileStore store2 = (IFileStore) o2;
+ //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;
+ }
+
+ 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);
+
+ }
+ };
+ }
+
+ 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_DELETE :
+ Resource link = (Resource) event.resource;
+ if (link.isLinked())
+ removeFromLocationsMap(link, link.getStore());
+ //fall through
+ 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--;
+ }
+
+ 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).length > 0)
+ changedProjects = true;
+ }
+
+ /* (non-Javadoc)
+ * @see IManager#shutdown(IProgressMonitor)
+ */
+ public void shutdown(IProgressMonitor monitor) {
+ workspace.removeResourceChangeListener(this);
+ locationsMap.clear();
+ }
+
+ /* (non-Javadoc)
+ * @see IManager#startup(IProgressMonitor)
+ */
+ 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.
+ */
+ 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();
+ for (Iterator it = aliases.iterator(); it.hasNext();) {
+ IResource alias = (IResource) 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
+ }
+ 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 = (IResource) it.next();
+ hadChanges = true;
+ if (!resource.isAccessible())
+ continue;
+ if (resource.isLinked())
+ addToLocationsMap(resource, ((Resource) resource).getStore());
+ }
+ }
+ changedLinks.clear();
+ if (hadChanges)
+ buildAliasedProjectsSet();
+ changedProjects = false;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * 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.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.
+ */
+ 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);
+ }
+
+ 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;
+ // ensure all resources under the affected project are
+ // reported as having encoding changes
+ ICharsetListenerFilter filter = new ICharsetListenerFilter() {
+
+ public IPath getRoot() {
+ // visit the project subtree
+ return project.getFullPath();
+ }
+
+ public boolean isAffected(ResourceInfo info, IPathRequestor requestor) {
+ // for now, mark all resources in the project as potential encoding resource changes
+ return true;
+ }
+ };
+ addToQueue(filter);
+ }
+
+ 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() {
+
+ public IPath getRoot() {
+ // visit all resources in the workspace
+ return Path.ROOT;
+ }
+
+ 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() {
+ 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 {
+ new ElementTreeIterator(workspace.getElementTree(), filter.getRoot()).iterate(visitor);
+ } catch (WrappedRuntimeException e) {
+ throw (CoreException) e.getTargetException();
+ }
+ if (monitor.isCanceled())
+ throw new OperationCanceledException();
+ }
+
+ private ICharsetListenerFilter removeFromQueue() {
+ synchronized (work) {
+ return (ICharsetListenerFilter) work.remove();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.jobs.InternalJob#run(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ 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() {
+ Platform.getContentTypeManager().removeContentTypeChangeListener(this);
+ }
+
+ public void startup() {
+ Platform.getContentTypeManager().addContentTypeChangeListener(this);
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,304 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.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.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(Set newChanges) {
+ if (newChanges.isEmpty())
+ return;
+ synchronized (asyncChanges) {
+ asyncChanges.addAll(newChanges);
+ asyncChanges.notify();
+ }
+ schedule(CHARSET_UPDATE_DELAY);
+ }
+
+ public IProject getNextChange() {
+ synchronized (asyncChanges) {
+ return asyncChanges.isEmpty() ? null : (IProject) asyncChanges.remove(asyncChanges.size() - 1);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.jobs.InternalJob#run(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ 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);
+ IProject 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;
+ try {
+ if (next.isAccessible()) {
+ Preferences projectPrefs = getPreferences(next, false);
+ if (projectPrefs != null)
+ projectPrefs.flush();
+ }
+ } catch (BackingStoreException e) {
+ // we got an error saving
+ String detailMessage = Messages.resources_savingEncoding;
+ result.add(new ResourceStatus(IResourceStatus.FAILED_SETTING_CHARSET, next.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()
+ */
+ public boolean shouldRun() {
+ synchronized (asyncChanges) {
+ return !asyncChanges.isEmpty();
+ }
+ }
+ }
+
+ class Listener implements IResourceChangeListener {
+
+ private void processEntryChanges(IResourceDelta projectDelta, Set projectsToSave) {
+ // check each resource with user-set encoding to see if it has
+ // been moved/deleted
+ boolean resourceChanges = false;
+ IProject currentProject = (IProject) projectDelta.getResource();
+ Preferences projectPrefs = getPreferences(currentProject, false);
+ if (projectPrefs == null)
+ // no preferences for this project, just bail
+ return;
+ String[] affectedResources;
+ try {
+ affectedResources = projectPrefs.keys();
+ } catch (BackingStoreException e) {
+ // problems with the project scope... we gonna 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 (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) {
+ resourceChanges = true;
+ // 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) {
+ // if moving, copy the setting for the new location
+ IProject targetProject = workspace.getRoot().getProject(memberDelta.getMovedToPath().segment(0));
+ Preferences targetPrefs = getPreferences(targetProject, true);
+ targetPrefs.put(getKeyFor(memberDelta.getMovedToPath()), currentValue);
+ if (targetProject != currentProject)
+ projectsToSave.add(targetProject);
+ }
+ }
+ }
+ if (resourceChanges)
+ projectsToSave.add(currentProject);
+ }
+
+ /**
+ * For any change to the encoding file or any resource with encoding
+ * set, just discard the cache for the corresponding project.
+ */
+ public void resourceChanged(IResourceChangeEvent event) {
+ IResourceDelta delta = event.getDelta();
+ if (delta == null)
+ return;
+ IResourceDelta[] projectDeltas = delta.getAffectedChildren();
+ // process each project in the delta
+ Set projectsToSave = new HashSet();
+ 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);
+ }
+ }
+
+ public static final String ENCODING_PREF_NODE = "encoding"; //$NON-NLS-1$
+ private static final String PROJECT_KEY = "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 encodingSettings = getPreferences(project, false);
+ if (encodingSettings == 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(resourcePath, encodingSettings, recurse);
+ }
+
+ String getKeyFor(IPath resourcePath) {
+ return resourcePath.segmentCount() > 1 ? resourcePath.removeFirstSegments(1).toString() : PROJECT_KEY;
+ }
+
+ Preferences getPreferences(IProject project, boolean create) {
+ if (create)
+ // create all nodes down to the one we are interested in
+ return new ProjectScope(project).getNode(ResourcesPlugin.PI_RESOURCES).node(ENCODING_PREF_NODE);
+ // 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(ResourcesPlugin.PI_RESOURCES))
+ return null;
+ node = node.node(ResourcesPlugin.PI_RESOURCES);
+ if (!node.nodeExists(ENCODING_PREF_NODE))
+ return null;
+ return node.node(ENCODING_PREF_NODE);
+ } 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(IPath resourcePath, Preferences encodingSettings, boolean recurse) {
+ String charset = encodingSettings.get(getKeyFor(resourcePath), null);
+ if (!recurse)
+ return charset;
+ while (charset == null && resourcePath.segmentCount() > 1) {
+ resourcePath = resourcePath.removeLastSegments(1);
+ charset = encodingSettings.get(getKeyFor(resourcePath), null);
+ }
+ // ensure we default to the workspace encoding if none is found
+ return charset == null ? ResourcesPlugin.getEncoding() : charset;
+ }
+
+ 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) {
+ org.eclipse.core.runtime.Preferences resourcesPreferences = ResourcesPlugin.getPlugin().getPluginPreferences();
+ if (newCharset != null)
+ resourcesPreferences.setValue(ResourcesPlugin.PREF_ENCODING, newCharset);
+ else
+ resourcesPreferences.setToDefault(ResourcesPlugin.PREF_ENCODING);
+ ResourcesPlugin.getPlugin().savePluginPreferences();
+ return;
+ }
+ // for all other cases, we set a property in the corresponding project
+ IProject project = workspace.getRoot().getProject(resourcePath.segment(0));
+ Preferences encodingSettings = getPreferences(project, true);
+ if (newCharset == null || newCharset.trim().length() == 0)
+ encodingSettings.remove(getKeyFor(resourcePath));
+ else
+ encodingSettings.put(getKeyFor(resourcePath), newCharset);
+ try {
+ // disable the listener so we don't react to changes made by ourselves
+ charsetListener.setDisabled(true);
+ // save changes
+ encodingSettings.flush();
+ } catch (BackingStoreException e) {
+ String message = Messages.resources_savingEncoding;
+ throw new ResourceException(IResourceStatus.FAILED_SETTING_CHARSET, project.getFullPath(), message, e);
+ } finally {
+ charsetListener.setDisabled(false);
+ }
+
+ }
+
+ public void shutdown(IProgressMonitor monitor) {
+ workspace.removeResourceChangeListener(listener);
+ if (charsetListener != null)
+ charsetListener.shutdown();
+ }
+
+ public void startup(IProgressMonitor monitor) {
+ job = new CharsetManagerJob();
+ listener = new Listener();
+ workspace.addResourceChangeListener(listener, IResourceChangeEvent.POST_CHANGE);
+ charsetListener = new CharsetDeltaJob(workspace);
+ charsetListener.startup();
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ComputeProjectOrder.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,543 @@
+/*******************************************************************************
+ * 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.*;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspace;
+
+/**
+ * Implementation of a sort algorithm for computing the project order. This
+ * algorithm handles cycles in the project reference graph in a reasonable way.
+ *
+ * @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
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.
+ *
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 = (Vertex) vertexMap.get(fromId);
+ Vertex toVertex = (Vertex) 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 = (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
+ // MapDFSVisit
.
+ // *
+ // * Although this method is not used, it is the basis of the
+ // * non-recursive DFS
method.
+ // *
+ * When there is an arbitrary choice, vertexes are ordered as supplied. + * Arranged projects in descending alphabetical order generally results in + * an order that builds "A" before "Z" when there are no other constraints. + *
+ * Ref: Cormen, Leiserson, and Rivest
IProject
)
+ * @param references a list of project references [A,B] meaning that A
+ * references B (element type: IProject[]
)
+ * @return an object describing the resulting project order
+ */
+ static IWorkspace.ProjectOrder computeProjectOrder(SortedSet projects, List references) {
+
+ // Step 1: Create the graph object.
+ final Digraph g1 = new Digraph();
+ // add vertexes
+ for (Iterator it = projects.iterator(); it.hasNext();) {
+ IProject project = (IProject) it.next();
+ g1.addVertex(project);
+ }
+ // add edges
+ for (Iterator it = references.iterator(); it.hasNext();) {
+ IProject[] ref = (IProject[]) it.next();
+ IProject p = ref[0];
+ IProject q = ref[1];
+ // p has a project 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();) {
+ final IProject project = (IProject) it.next();
+ g2.addVertex(project);
+ }
+ // add edges
+ for (Iterator it = references.iterator(); it.hasNext();) {
+ IProject[] ref = (IProject[]) it.next();
+ IProject p = ref[0];
+ IProject q = ref[1];
+ // p has a project 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 sortedProjectList = g2.idsByDFSFinishTime(true);
+ IProject[] orderedProjects = new IProject[sortedProjectList.size()];
+ sortedProjectList.toArray(orderedProjects);
+ IProject[][] knots;
+ boolean hasCycles = g2.containsCycles();
+ if (hasCycles) {
+ List knotList = g2.nonTrivialComponents();
+ knots = new IProject[knotList.size()][];
+ // cannot use knotList.toArray(knots) because each knot is Object[]
+ // and we need each to be an IProject[]
+ int k = 0;
+ for (Iterator it = knotList.iterator(); it.hasNext();) {
+ Object[] knot = (Object[]) it.next();
+ IProject[] knotCopy = new IProject[knot.length];
+ for (int i = 0; i < knot.length; i++) {
+ knotCopy[i] = (IProject) knot[i];
+ }
+ knots[k] = knotCopy;
+ k++;
+ }
+ } else {
+ knots = new IProject[][] {};
+ }
+ return new IWorkspace.ProjectOrder(orderedProjects, hasCycles, knots);
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Container.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,298 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.internal.resources;
+
+import java.util.*;
+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.
+ */
+ 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();
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#exists(IPath)
+ */
+ public boolean exists(IPath childPath) {
+ return workspace.getResourceInfo(getFullPath().append(childPath), false, false) != null;
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#findMember(String)
+ */
+ public IResource findMember(String name) {
+ return findMember(name, false);
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#findMember(String, boolean)
+ */
+ public IResource findMember(String name, boolean phantom) {
+ IPath childPath = getFullPath().append(name);
+ ResourceInfo info = workspace.getResourceInfo(childPath, phantom, false);
+ return info == null ? null : workspace.newResource(childPath, info.getType());
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#findMember(IPath)
+ */
+ public IResource findMember(IPath childPath) {
+ return findMember(childPath, false);
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#findMember(IPath)
+ */
+ 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());
+ }
+
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IFolder#getFile(String) and IProject#getFile(String)
+ */
+ public IFile getFile(String name) {
+ return (IFile) workspace.newResource(getFullPath().append(name), FILE);
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#getFile(IPath)
+ */
+ public IFile getFile(IPath childPath) {
+ return (IFile) workspace.newResource(getFullPath().append(childPath), FILE);
+ }
+
+ /* (non-Javadoc)
+ * @see IFolder#getFolder(String) and IProject#getFolder(String)
+ */
+ public IFolder getFolder(String name) {
+ return (IFolder) workspace.newResource(getFullPath().append(name), FOLDER);
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#getFolder(IPath)
+ */
+ public IFolder getFolder(IPath childPath) {
+ return (IFolder) workspace.newResource(getFullPath().append(childPath), FOLDER);
+ }
+
+ /**
+ * @deprecated
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#members()
+ */
+ public IResource[] members() throws CoreException {
+ // forward to central method
+ return members(IResource.NONE);
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#members(boolean)
+ */
+ public IResource[] members(boolean phantom) throws CoreException {
+ // forward to central method
+ return members(phantom ? INCLUDE_PHANTOMS : IResource.NONE);
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#members(int)
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#getDefaultCharset()
+ */
+ public String getDefaultCharset() throws CoreException {
+ return getDefaultCharset(true);
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#findDeletedMembersWithHistory(int, IProgressMonitor)
+ */
+ 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 {
+ Set allFilePaths = historyStore.allFiles(basePath, depth, monitor);
+ // convert IPaths to IFiles keeping only files that no longer exist
+ for (Iterator it = allFilePaths.iterator(); it.hasNext();) {
+ IPath filePath = (IPath) it.next();
+ IFile file = root.getFile(filePath);
+ if (!file.exists()) {
+ deletedFiles.add(file);
+ }
+ }
+ }
+ return (IFile[]) deletedFiles.toArray(new IFile[deletedFiles.size()]);
+ }
+
+ /** (non-Javadoc)
+ * @see IContainer#setDefaultCharset(String)
+ * @deprecated Replaced by {@link #setDefaultCharset(String, IProgressMonitor)} which
+ * is a workspace operation and reports changes in resource deltas.
+ */
+ public void setDefaultCharset(String charset) throws CoreException {
+ ResourceInfo info = getResourceInfo(false, false);
+ checkAccessible(getFlags(info));
+ workspace.getCharsetManager().setCharsetFor(getFullPath(), charset);
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#setDefaultCharset(String, IProgressMonitor)
+ */
+ 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;
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ContentDescriptionManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,484 @@
+/*******************************************************************************
+ * 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
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+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)
+ */
+ public boolean belongsTo(Object family) {
+ return FAMILY_DESCRIPTION_CACHE_FLUSH.equals(family);
+ }
+
+ /* (non-Javadoc)
+ * See WorkspaceJob#runInWorkspace(IProgressMonitor)
+ */
+ 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 : (IPath[]) 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;
+ }
+
+ public int available() throws IOException {
+ if (actual == null)
+ return 0;
+ return actual.available();
+ }
+
+ 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());
+ }
+ }
+
+ public int read() throws IOException {
+ ensureOpened();
+ return actual.read();
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ ensureOpened();
+ return actual.read(b, off, len);
+ }
+
+ 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;
+
+ 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 IContentTypeManager.IContentTypeChangeListener#contentTypeChanged(IContentTypeManager.ContentTypeChangeEvent)
+ */
+ 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) {
+ 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() {
+ 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);
+ }
+
+ public IContentDescription getDescriptionFor(File file, ResourceInfo info) throws CoreException {
+ if (ProjectContentTypes.usesContentTypePreferences(file.getFullPath().segment(0)))
+ // caching for project containing project specific settings is not supported
+ return readDescription(file);
+ switch (getCacheState()) {
+ case INVALID_CACHE :
+ // the cache is not good, flush it
+ flushJob.schedule(1000);
+ //fall through and just read the file
+ case FLUSHING_CACHE :
+ // the cache is being flushed, but is still not good, just read the file
+ return readDescription(file);
+ }
+ // 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);
+ }
+ synchronized (this) {
+ // tries to get a description from the cache
+ Cache.Entry entry = cache.getEntry(file.getFullPath());
+ if (entry != null && entry.getTimestamp() == info.getContentId())
+ // there was a description in the cache, and it was up to date
+ return (IContentDescription) entry.getCached();
+ // we are going to add an entry to the cache or update the resource info - remember that
+ setCacheState(USED_CACHE);
+ // either we didn't find a description in the cache, or it was not up-to-date - has to be read again
+ IContentDescription newDescription = readDescription(file);
+ 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, info.getContentId());
+ else {
+ // just update the existing entry
+ entry.setTimestamp(info.getContentId());
+ entry.setCached(newDescription);
+ }
+ return newDescription;
+ }
+ }
+
+ /**
+ * 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)
+ 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 {
+ file.ensureClosed(contents);
+ }
+ }
+
+ /**
+ * @see IRegistryChangeListener#registryChanged(IRegistryChangeEvent)
+ */
+ 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)
+ */
+ public void handleEvent(LifecycleEvent event) {
+ //TODO are these the only events we care about?
+ switch (event.kind) {
+ case LifecycleEvent.PRE_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));
+ }
+
+ 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());
+ Platform.getContentTypeManager().removeContentTypeChangeListener(this);
+ Platform.getExtensionRegistry().removeRegistryChangeListener(this);
+ cache.dispose();
+ cache = null;
+ flushJob.cancel();
+ flushJob = null;
+ projectContentTypes = null;
+ }
+
+ 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)
+ // 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/DelayedSnapshotJob.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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.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()
+ */
+ public IStatus run(IProgressMonitor monitor) {
+ if (monitor.isCanceled())
+ return Status.CANCEL_STATUS;
+ if (ResourcesPlugin.getWorkspace() == null)
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,482 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.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;
+
+public class File extends Resource implements IFile {
+
+ protected File(IPath path, Workspace container) {
+ super(path, container);
+ }
+
+ /* (non-Javadoc)
+ * @see IFile#appendContents(InputStream, int, IProgressMonitor)
+ */
+ 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();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IFile#appendContents(InputStream, boolean, boolean, IProgressMonitor)
+ */
+ 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; + } + + /** + * Checks that this resource is synchronized with the local file system. + */ + private void checkSynchronized() throws CoreException { + if (!isSynchronized(IResource.DEPTH_ZERO)) { + String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, getFullPath()); + throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, getFullPath(), message, null); + } + } + + /* (non-Javadoc) + * @see IFile#create(InputStream, int, IProgressMonitor) + */ + 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)); + + 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 + } + } + 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(); + ensureClosed(content); + } + } + + /* (non-Javadoc) + * @see IFile#create(InputStream, boolean, IProgressMonitor) + */ + 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); + } + + /** + * IFile API methods require that the stream be closed regardless + * of the success of the method. This method makes a best effort + * at closing the stream, and ignores any resulting IOException. + */ + protected void ensureClosed(InputStream stream) { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + // ignore + } + } + } + + /* (non-Javadoc) + * @see IFile#getCharset() + */ + public String getCharset() throws CoreException { + return getCharset(true); + } + + /* (non-Javadoc) + * @see IFile#getCharset(boolean) + */ + 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); + } + + /* (non-Javadoc) + * @see IFile#getCharsetFor(Reader) + */ + 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); + 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); + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.resources.IFile#getContentDescription() + */ + public IContentDescription getContentDescription() throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + checkSynchronized(); + checkLocal(flags, DEPTH_ZERO); + return workspace.getContentDescriptionManager().getDescriptionFor(this, info); + } + + /* (non-Javadoc) + * @see IFile#getContents() + */ + public InputStream getContents() throws CoreException { + return getContents(false); + } + + /* (non-Javadoc) + * @see IFile#getContents(boolean) + */ + 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); + } + + /** + * @see IFile#getEncoding() + * @deprecated + */ + public int getEncoding() throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + int flags = getFlags(info); + checkAccessible(flags); + checkLocal(flags, DEPTH_ZERO); + return getLocalManager().getEncoding(this); + } + + /* (non-Javadoc) + * @see IFile#getHistory(IProgressMonitor) + */ + public IFileState[] getHistory(IProgressMonitor monitor) { + return getLocalManager().getHistoryStore().getStates(getFullPath(), monitor); + } + + /* (non-Javadoc) + * @see IResource#getType() + */ + 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. + */ + public void refreshLocal(int depth, IProgressMonitor monitor) throws CoreException { + if (!getLocalManager().fastIsSynchronized(this)) + super.refreshLocal(IResource.DEPTH_ZERO, monitor); + } + + /* (non-Javadoc) + * @see IFile#setContents(IFileState, int, IProgressMonitor) + */ + public void setContents(IFileState content, int updateFlags, IProgressMonitor monitor) throws CoreException { + setContents(content.getContents(), updateFlags, monitor); + } + + /* (non-Javadoc) + * @see IFile#setContents(InputStream, int, IProgressMonitor) + */ + 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(); + ensureClosed(content); + } + } + + /* (non-Javadoc) + * @see IResource#setLocalTimeStamp(long) + */ + 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) getProject()).updateDescription(); + return; + } + // check to see if we are in the .settings directory + if (count == 3 && EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME.equals(name)) { + ProjectPreferences.updatePreferences(this); + return; + } + } + + /** (non-Javadoc) + * @see IFile#setCharset(String) + * @deprecated Replaced by {@link #setCharset(String, IProgressMonitor)} which + * is a workspace operation and reports changes in resource deltas. + */ + public void setCharset(String newCharset) throws CoreException { + ResourceInfo info = getResourceInfo(false, false); + checkAccessible(getFlags(info)); + workspace.getCharsetManager().setCharsetFor(getFullPath(), newCharset); + } + + /* (non-Javadoc) + * @see IFile#setCharset(String, IProgressMonitor) + */ + 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(); + } + } + + /* (non-Javadoc) + * @see IFile#setContents(InputStream, boolean, boolean, IProgressMonitor) + */ + 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); + } + + /* (non-Javadoc) + * @see IFile#setContents(IFileState, boolean, boolean, IProgressMonitor) + */ + 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/FileState.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2000, 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.internal.resources; + +import java.io.*; +import org.eclipse.core.internal.localstore.IHistoryStore; +import org.eclipse.core.internal.utils.Messages; +import org.eclipse.core.internal.utils.UniversalUniqueIdentifier; +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() + */ + public boolean exists() { + return store.exists(this); + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IEncodedStorage#getCharset() + */ + 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()); + boolean failed = false; + try { + IContentDescription description = contentTypeManager.getDescriptionFor(contents, getName(), new QualifiedName[] {IContentDescription.CHARSET}); + return description == null ? null : description.getCharset(); + } catch (IOException e) { + failed = true; + String message = NLS.bind(Messages.history_errorContentDescription, getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_DESCRIBING_CONTENTS, getFullPath(), message, e); + } finally { + try { + contents.close(); + } catch (IOException e) { + if (!failed) { + String message = NLS.bind(Messages.history_errorContentDescription, getFullPath()); + throw new ResourceException(IResourceStatus.FAILED_DESCRIBING_CONTENTS, getFullPath(), message, e); + } + } + } + } + + /* (non-Javadoc) + * @see IFileState#getContents() + */ + public InputStream getContents() throws CoreException { + return store.getContents(this); + } + + /* (non-Javadoc) + * @see IFileState#getFullPath() + */ + public IPath getFullPath() { + return fullPath; + } + + /* (non-Javadoc) + * @see IFileState#getModificationTime() + */ + public long getModificationTime() { + return lastModified; + } + + /* (non-Javadoc) + * @see IFileState#getName() + */ + public String getName() { + return fullPath.lastSegment(); + } + + public UniversalUniqueIdentifier getUUID() { + return uuid; + } + + /* (non-Javadoc) + * @see IFileState#isReadOnly() + */ + public boolean isReadOnly() { + return true; + } + + /** + * Returns a string representation of this object. Used for debug only. + */ + 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Folder.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,184 @@ +/******************************************************************************* + * 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.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)); + + 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IFolder#create(int, boolean, IProgressMonitor)
+ */
+ public void create(int updateFlags, boolean local, IProgressMonitor monitor) throws CoreException {
+ 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();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IFolder#create(boolean, boolean, IProgressMonitor)
+ */
+ 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);
+ internalCreate(IResource.FORCE, true, monitor);
+ }
+
+ /* (non-Javadoc)
+ * @see IContainer#getDefaultCharset(boolean)
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getType()
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ICoreConstants.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.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;
+ /**
+ * 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 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IMarkerSetElement.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IMarkerSetElement.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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 IModelObjectConstants {
+ public static final String ARGUMENTS = "arguments"; //$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 FILE_STATE_LONGEVITY = "fileStateLongevity"; //$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 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$
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalTeamHook.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalWorkspaceJob.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/InternalWorkspaceJob.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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.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();
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LinkDescription.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.internal.resources;
+
+import java.net.URI;
+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 {
+
+ 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;
+ }
+
+ public boolean equals(Object o) {
+ if (!(o.getClass() == LinkDescription.class))
+ return false;
+ LinkDescription other = (LinkDescription) o;
+ return localLocation.equals(other.localLocation) && 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 int hashCode() {
+ return type + 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#reconcileLinks(ProjectDescription)}.
+ */
+ public int compareTo(Object o) {
+ LinkDescription that = (LinkDescription) o;
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocalMetaArea.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,399 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.internal.resources;
+
+import java.io.*;
+import java.net.URI;
+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.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 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_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());
+ }
+
+ 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);
+ }
+
+ 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$
+ }
+
+ public IPath getSnapshotLocationFor(IResource resource) {
+ 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 and the project's dynamic references 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
+ */
+ public void readPrivateDescription(IProject target, IProjectDescription 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);
+ } 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
+ */
+ 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);
+ } 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[] references = desc.getDynamicReferences(false);
+ final int numRefs = references.length;
+ if (projectLocation == null && numRefs == 0)
+ 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(numRefs);
+ for (int i = 0; i < numRefs; i++)
+ dataOut.writeUTF(references[i].getName());
+ output.succeed();
+ } finally {
+ dataOut.close();
+ }
+ } catch (IOException e) {
+ String message = NLS.bind(Messages.resources_exSaveProjectLocation, target.getName());
+ throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e);
+ }
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/LocationValidator.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,430 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.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()) {
+ IPath pathPart = new Path(location.getSchemeSpecificPart());
+ String message;
+ 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 = workspace.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) {
+ 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 = workspace.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 = 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#validateProjectLocation(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 = 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
+ IPath parentPath = locationPath.removeLastSegments(1);
+ if (FileUtil.isPrefixOf(parentPath, defaultDefaultLocation) && FileUtil.isPrefixOf(defaultDefaultLocation, parentPath)) {
+ 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];
+ // 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();
+ URI testLocation = desc.getLocationURI();
+ // if the project uses the default location then continue
+ if (testLocation == null)
+ continue;
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Marker.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,315 @@
+/*******************************************************************************
+ * 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.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() + */ + 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) + */ + 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() + */ + public boolean exists() { + return getInfo() != null; + } + + /** + * @see IMarker#getAttribute(String) + */ + public Object getAttribute(String attributeName) throws CoreException { + Assert.isNotNull(attributeName); + MarkerInfo info = getInfo(); + checkInfo(info); + return info.getAttribute(attributeName); + } + + /** + * @see IMarker#getAttribute(String, int) + */ + 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) + */ + 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) + */ + 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() + */ + public Map getAttributes() throws CoreException { + MarkerInfo info = getInfo(); + checkInfo(info); + return info.getAttributes(); + } + + /** + * @see IMarker#getAttributes(String[]) + */ + public Object[] getAttributes(String[] attributeNames) throws CoreException { + Assert.isNotNull(attributeNames); + MarkerInfo info = getInfo(); + checkInfo(info); + return info.getAttributes(attributeNames); + } + + /** + * @see IMarker#getCreationTime() + */ + public long getCreationTime() throws CoreException { + MarkerInfo info = getInfo(); + checkInfo(info); + return info.getCreationTime(); + } + + /** + * @see IMarker#getId() + */ + public long getId() { + return id; + } + + protected MarkerInfo getInfo() { + return getWorkspace().getMarkerManager().findMarkerInfo(resource, id); + } + + /** + * @see IMarker#getResource() + */ + public IResource getResource() { + return resource; + } + + /** + * @see IMarker#getType() + */ + 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();
+ }
+
+ public int hashCode() {
+ return (int) id + resource.hashCode();
+ }
+
+ /**
+ * @see IMarker#isSubtypeOf(String)
+ */
+ public boolean isSubtypeOf(String type) throws CoreException {
+ return getWorkspace().getMarkerManager().isSubtype(getType(), type);
+ }
+
+ /**
+ * @see IMarker#setAttribute(String, int)
+ */
+ public void setAttribute(String attributeName, int value) throws CoreException {
+ setAttribute(attributeName, new Integer(value));
+ }
+
+ /**
+ * @see IMarker#setAttribute(String, Object)
+ */
+ 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;
+ markerInfo.setAttribute(attributeName, value);
+ 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)
+ */
+ public void setAttribute(String attributeName, boolean value) throws CoreException {
+ setAttribute(attributeName, value ? Boolean.TRUE : Boolean.FALSE);
+ }
+
+ /**
+ * @see IMarker#setAttributes(String[], Object[])
+ */
+ 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;
+ markerInfo.setAttributes(attributeNames, values);
+ 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)
+ */
+ 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;
+ markerInfo.setAttributes(values);
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerAttributeMap.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,308 @@
+/*******************************************************************************
+ * 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.*;
+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.
+ */
+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;
+
+ /**
+ * Creates a new marker attribute map of default size
+ */
+ public MarkerAttributeMap() {
+ super();
+ }
+
+ /**
+ * Creates a new marker attribute map.
+ * @param initialCapacity The initial number of elements that will fit in the map.
+ */
+ public MarkerAttributeMap(int initialCapacity) {
+ elements = new Object[Math.max(initialCapacity * 2, 0)];
+ }
+
+ /**
+ * 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()
+ */
+ public void clear() {
+ elements = null;
+ count = 0;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#containsKey(java.lang.Object)
+ */
+ public boolean containsKey(Object key) {
+ key = ((String) key).intern();
+ if (elements == null || count == 0)
+ return false;
+ 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)
+ */
+ 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;
+ }
+
+ /* (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.
+ */
+ public Set entrySet() {
+ return toHashMap().entrySet();
+ }
+
+ /* (non-Javadoc)
+ * @see Object#equals(java.lang.Object)
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#get(java.lang.Object)
+ */
+ public Object get(Object key) {
+ key = ((String) key).intern();
+ if (elements == null || count == 0)
+ return null;
+ for (int i = 0; i < elements.length; i = i + 2)
+ if (elements[i] == key)
+ return 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()
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#isEmpty()
+ */
+ 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.
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#put(java.lang.Object, java.lang.Object)
+ */
+ public Object put(Object key, Object value) {
+ if (key == null)
+ throw new NullPointerException();
+ if (value == null)
+ return remove(key);
+ key = ((String) key).intern();
+
+ // 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;
+ }
+
+ // 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 oldValue;
+ }
+ }
+
+ // otherwise add it to the list of elements.
+ // grow if necessary
+ if (elements.length <= (count * 2))
+ grow();
+ 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)
+ */
+ 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);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see Map#remove(java.lang.Object)
+ */
+ public Object remove(Object key) {
+ key = ((String) key).intern();
+ if (elements == null || count == 0)
+ return null;
+ 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 result;
+ }
+ }
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see Map#size()
+ */
+ public int size() {
+ return count;
+ }
+
+ /* (non-Javadoc
+ * Method declared on IStringPoolParticipant
+ */
+ 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());
+ for (int i = 0; i < elements.length; i = i + 2) {
+ if (elements[i] != null) {
+ result.put(elements[i], 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.
+ */
+ 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;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDelta.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,233 @@
+/*******************************************************************************
+ * 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.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)
+ */
+ public Object getAttribute(String attributeName) {
+ return info.getAttribute(attributeName);
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getAttribute(String, int)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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()
+ */
+ public Map getAttributes() {
+ return info.getAttributes();
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getAttributes(String[])
+ */
+ public Object[] getAttributes(String[] attributeNames) {
+ return info.getAttributes(attributeNames);
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getId()
+ */
+ public long getId() {
+ return info.getId();
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getKind()
+ */
+ public int getKind() {
+ return kind;
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getMarker()
+ */
+ public IMarker getMarker() {
+ return new Marker(resource, getId());
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getResource()
+ */
+ public IResource getResource() {
+ return resource;
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#getType()
+ */
+ public String getType() {
+ return info.getType();
+ }
+
+ /* (non-Javadoc)
+ * @see IMarkerDelta#isSubtypeOf(String)
+ */
+ 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 = (IPath) it.next();
+ MarkerSet oldSet = (MarkerSet) oldChanges.get(key);
+ MarkerSet newSet = (MarkerSet) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerDeltaManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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 java.util.*;
+
+/**
+ * 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];
+ 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.
+ */
+ 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;
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerInfo.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,196 @@
+/*******************************************************************************
+ * 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.io.UnsupportedEncodingException;
+import java.util.Map;
+import org.eclipse.core.internal.utils.IStringPoolParticipant;
+import org.eclipse.core.internal.utils.StringPool;
+import org.eclipse.core.runtime.Assert;
+
+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();
+ }
+
+ public MarkerInfo() {
+ super();
+ }
+
+ /**
+ * See Object#clone.
+ */
+ 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;
+ }
+
+ 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) {
+ 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) {
+ if (map == null)
+ attributes = null;
+ else
+ attributes = new MarkerAttributeMap(map);
+ }
+
+ public void setAttributes(String[] attributeNames, Object[] values) {
+ Assert.isTrue(attributeNames.length == values.length);
+ for (int i = 0; i < attributeNames.length; i++)
+ setAttribute(attributeNames[i], values[i]);
+ }
+
+ 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
+ */
+ public void shareStrings(StringPool set) {
+ type = set.add(type);
+ Map map = attributes;
+ if (map instanceof IStringPoolParticipant)
+ ((IStringPoolParticipant) map).shareStrings(set);
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,653 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.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 (MarkerInfo[]) 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
+ markers.removeAll(matching);
+ info.setMarkers(markers.size() == 0 ? null : 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 = (MarkerSet) 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 (IMarker[]) 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 = (MarkerSet) 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 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() {
+ 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() {
+ public String requestName() {
+ return path.lastSegment();
+ }
+
+ 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)
+ */
+ 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)
+ */
+ 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() {
+ 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;
+
+ 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() {
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_1.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * 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.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
+ */
+ 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, (IMarkerSetElement[]) 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((String) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_2.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * 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.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
+ */
+ 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, (IMarkerSetElement[]) 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((String) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerReader_3.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * 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.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
+ */
+ 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, (IMarkerSetElement[]) 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((String) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSet.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSet.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,244 @@
+/*******************************************************************************
+ * 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.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]);
+ }
+
+ protected Object clone() {
+ try {
+ MarkerSet copy = (MarkerSet) super.clone();
+ //copy the attribute array
+ copy.elements = (IMarkerSetElement[]) 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
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_1.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * 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.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
+ */
+ 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((String) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerSnapshotReader_2.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * 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.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
+ */
+ 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((String) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/MarkerTypeDefinitionCache.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * 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.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 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) {
+ if (this.listeners.size() == 0)
+ return;
+ // use a separate collection to avoid interference of simultaneous additions/removals
+ Object[] listenerArray = this.listeners.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() {
+ public void handleException(Throwable exception) {
+ // already being logged in SafeRunner#run()
+ }
+
+ public void run() throws Exception {
+ l.pathVariableChanged(pve);
+ }
+ };
+ SafeRunner.run(job);
+ }
+ }
+
+ /**
+ * Return a key to use in the Preferences.
+ */
+ private String getKeyForName(String varName) {
+ return VARIABLE_PREFIX + varName;
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IPathVariableManager#getPathVariableNames()
+ */
+ 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 (String[]) 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)
+ */
+ 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)
+ */
+ public boolean isDefined(String varName) {
+ return getValue(varName) != null;
+ }
+
+ /**
+ * @see org.eclipse.core.resources.
+ * IPathVariableManager#removeChangeListener(IPathVariableChangeListener)
+ */
+ public void removeChangeListener(IPathVariableChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IPathVariableManager#resolvePath(IPath)
+ */
+ 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));
+ }
+
+ public URI resolveURI(URI uri) {
+ if (uri == null || uri.isAbsolute())
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PlatformURLResourceConnection.java Mon Jun 01 10:16:20 2009 -0500
@@ -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.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.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Platform URL support
+ * platform:/resource/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 = workspace.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);
+
+ 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) {
+ 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);
+
+ 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) {
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#clearHistory(IProgressMonitor)
+ */
+ public void clearHistory(IProgressMonitor monitor) {
+ getLocalManager().getHistoryStore().remove(getFullPath(), monitor);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see ISchedulingRule#contains(ISchedulingRule)
+ */
+ 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;
+ return path.isPrefixOf(((IResource) rule).getFullPath());
+ }
+
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#copy(IPath, boolean, IProgressMonitor)
+ */
+ public void copy(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException {
+ int updateFlags = force ? IResource.FORCE : IResource.NONE;
+ copy(destination, updateFlags, monitor);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#copy(IPath, int, IProgressMonitor)
+ */
+ 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();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#copy(IProjectDescription, boolean, IProgressMonitor)
+ */
+ 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)
+ */
+ 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);
+ 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) {
+ IResource existing = workspace.getRoot().findMember(getFullPath());
+ if (existing != null)
+ workspace.deleteResource(existing);
+ }
+ ResourceInfo info = workspace.createResource(this, false);
+ info.set(M_LINK);
+ localLocation = FileUtil.canonicalURI(localLocation);
+ getLocalManager().link(this, localLocation, fileInfo);
+ monitor.worked(Policy.opWork * 5 / 100);
+ //save the location in the project description
+ Project project = (Project) getProject();
+ project.internalGetDescription().setLinkLocation(getProjectRelativePath(), new LinkDescription(this, localLocation));
+ 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();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#createMarker(String)
+ */
+ 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);
+ }
+ }
+
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#delete(boolean, IProgressMonitor)
+ */
+ public void delete(boolean force, IProgressMonitor monitor) throws CoreException {
+ delete(force ? IResource.FORCE : IResource.NONE, monitor);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#delete(int, IProgressMonitor)
+ */
+ 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();
+ 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);
+ //make sure the rule factory is cleared on project deletion
+ if (getType() == PROJECT)
+ ((Rules) workspace.getRuleFactory()).setRuleFactory((IProject) this, null);
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } finally {
+ workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork * 1000));
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#deleteMarkers(String, boolean, int)
+ */
+ 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, (IResource) it.next()));
+
+ // check if we deleted a preferences file
+ ProjectPreferences.deleted(this);
+
+ /* 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);
+
+ //remove all deleted linked resources from the project description
+ if (getType() != IResource.PROJECT && links != null) {
+ Project project = (Project) getProject();
+ ProjectDescription description = project.internalGetDescription();
+ for (Iterator it = links.iterator(); it.hasNext();)
+ description.setLinkLocation(((IResource) 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 = (LinkDescription) 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#equals(Object)
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#exists()
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#findMarker(long)
+ */
+ public IMarker findMarker(long id) {
+ return workspace.getMarkerManager().findMarker(this, id);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#findMarkers(String, boolean, int)
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#findMaxProblemSeverity(String, boolean, int)
+ */
+ 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.equalsIgnoreCase(list[i]))
+ 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()) {
+ Project project = (Project) getProject();
+ project.internalGetDescription().setLinkLocation(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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getFileExtension()
+ */
+ 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();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getFullPath()
+ */
+ public IPath getFullPath() {
+ return path;
+ }
+
+ public FileSystemResourceManager getLocalManager() {
+ return workspace.getFileSystemManager();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getLocalTimeStamp()
+ */
+ public long getLocalTimeStamp() {
+ ResourceInfo info = getResourceInfo(false, false);
+ return info == null ? IResource.NULL_STAMP : info.getLocalSyncInfo();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getLocation()
+ */
+ public IPath getLocation() {
+ IProject project = getProject();
+ if (project != null && !project.exists())
+ return null;
+ return getLocalManager().locationFor(this);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getLocation()
+ */
+ public URI getLocationURI() {
+ IProject project = getProject();
+ if (project != null && !project.exists())
+ return null;
+ return getLocalManager().locationURIFor(this);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getMarker(long)
+ */
+ public IMarker getMarker(long id) {
+ return new Marker(this, id);
+ }
+
+ protected MarkerManager getMarkerManager() {
+ return workspace.getMarkerManager();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getModificationStamp()
+ */
+ public long getModificationStamp() {
+ ResourceInfo info = getResourceInfo(false, false);
+ return info == null ? IResource.NULL_STAMP : info.getModificationStamp();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getName()
+ */
+ public String getName() {
+ return path.lastSegment();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getParent()
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getPersistentProperty(QualifiedName)
+ */
+ public String getPersistentProperty(QualifiedName key) throws CoreException {
+ checkAccessibleAndLocal(DEPTH_ZERO);
+ return getPropertyManager().getProperty(this, key);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getPersistentProperties()
+ */
+ public Map getPersistentProperties() throws CoreException {
+ checkAccessibleAndLocal(DEPTH_ZERO);
+ return getPropertyManager().getProperties(this);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getProject()
+ */
+ public IProject getProject() {
+ return workspace.getRoot().getProject(path.segment(0));
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getProjectRelativePath()
+ */
+ public IPath getProjectRelativePath() {
+ return getFullPath().removeFirstSegments(ICoreConstants.PROJECT_SEGMENT_LENGTH);
+ }
+
+ public IPropertyManager getPropertyManager() {
+ return workspace.getPropertyManager();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getRawLocation()
+ */
+ public IPath getRawLocation() {
+ if (isLinked())
+ return FileUtil.toPath(((Project) getProject()).internalGetDescription().getLinkLocationURI(getProjectRelativePath()));
+ return getLocation();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getRawLocation()
+ */
+ public URI getRawLocationURI() {
+ if (isLinked())
+ return ((Project) getProject()).internalGetDescription().getLinkLocationURI(getProjectRelativePath());
+ return getLocationURI();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getResourceAttributes()
+ */
+ public ResourceAttributes getResourceAttributes() {
+ if (!isAccessible())
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getSessionProperty(QualifiedName)
+ */
+ public Object getSessionProperty(QualifiedName key) throws CoreException {
+ ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO);
+ return info.getSessionProperty(key);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getSessionProperties()
+ */
+ public Map getSessionProperties() throws CoreException {
+ ResourceInfo info = checkAccessibleAndLocal(DEPTH_ZERO);
+ return info.getSessionProperties();
+ }
+
+ public IFileStore getStore() {
+ return getLocalManager().getStore(this);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getType()
+ */
+ 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$
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#getWorkspace()
+ */
+ public IWorkspace getWorkspace() {
+ return workspace;
+ }
+
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#isAccessible()
+ */
+ public boolean isAccessible() {
+ return exists();
+ }
+
+ /* (non-Javadoc)
+ * @see ISchedulingRule#isConflicting(ISchedulingRule)
+ */
+ public boolean isConflicting(ISchedulingRule rule) {
+ //must not schedule at same time as notification
+ if (rule.getClass().equals(WorkManager.NotifyRule.class))
+ return true;
+ if (!(rule instanceof IResource))
+ return false;
+ IPath otherPath = ((IResource) rule).getFullPath();
+ return path.isPrefixOf(otherPath) || otherPath.isPrefixOf(path);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#isDerived()
+ */
+ public boolean isDerived() {
+ return isDerived(IResource.NONE);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#isDerived(int)
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#isHidden()
+ */
+ public boolean isHidden() {
+ ResourceInfo info = getResourceInfo(false, false);
+ int flags = getFlags(info);
+ return flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_HIDDEN);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#isLinked()
+ */
+ public boolean isLinked() {
+ return isLinked(NONE);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#isLinked()
+ */
+ 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 (((LinkDescription) 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);
+ }
+
+ /**
+ * @see IResource#isLocal(int)
+ * @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
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#isPhantom()
+ */
+ 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);
+ }
+
+ /** (non-Javadoc)
+ * @see IResource#isReadOnly()
+ * @deprecated
+ */
+ public boolean isReadOnly() {
+ final ResourceAttributes attributes = getResourceAttributes();
+ return attributes == null ? false : attributes.isReadOnly();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#isSynchronized(int)
+ */
+ public boolean isSynchronized(int depth) {
+ return getLocalManager().isSynchronized(this, depth);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#isTeamPrivateMember()
+ */
+ public boolean isTeamPrivateMember() {
+ ResourceInfo info = getResourceInfo(false, false);
+ int flags = getFlags(info);
+ return flags != NULL_FLAG && ResourceInfo.isSet(flags, ICoreConstants.M_TEAM_PRIVATE_MEMBER);
+ }
+
+ /**
+ * 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#move(IPath, boolean, IProgressMonitor)
+ */
+ public void move(IPath destination, boolean force, IProgressMonitor monitor) throws CoreException {
+ move(destination, force ? IResource.FORCE : IResource.NONE, monitor);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#move(IPath, int, IProgressMonitor)
+ */
+ 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());
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } finally {
+ workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#move(IProjectDescription, boolean, IProgressMonitor)
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#move(IPath, int, IProgressMonitor)
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#refreshLocal(int, IProgressMonitor)
+ */
+ 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;
+ workspace.beginOperation(true);
+ if (getType() == IResource.PROJECT)
+ workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_REFRESH, this));
+ build = getLocalManager().refresh(this, depth, true, Policy.subMonitorFor(monitor, Policy.opWork));
+ } catch (OperationCanceledException e) {
+ workspace.getWorkManager().operationCanceled();
+ throw e;
+ } catch (Error e) {
+ //support to track down Bug 95089
+ Policy.log(e);
+ throw e;
+ } catch (RuntimeException e) {
+ //support to track down Bug 95089
+ Policy.log(e);
+ throw e;
+ } finally {
+ workspace.endOperation(rule, build, Policy.subMonitorFor(monitor, Policy.endOpWork));
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /* (non-Javadoc)
+ * Method declared on {@link IPathRequestor}.
+ */
+ public String requestName() {
+ return getName();
+ }
+
+ /* (non-Javadoc)
+ * Method declared on {@link IPathRequestor}.
+ */
+ public IPath requestPath() {
+ return getFullPath();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#revertModificationStamp
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#setDerived(boolean)
+ */
+ 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);
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#setHidden(boolean)
+ */
+ 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);
+ }
+ }
+
+ /**
+ * @see IResource#setLocal(boolean, int, IProgressMonitor)
+ * @deprecated
+ */
+ 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();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#setLocalTimeStamp(long)
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#setPersistentProperty(QualifiedName, String)
+ */
+ public void setPersistentProperty(QualifiedName key, String value) throws CoreException {
+ checkAccessibleAndLocal(DEPTH_ZERO);
+ getPropertyManager().setProperty(this, key, value);
+ }
+
+ /** (non-Javadoc)
+ * @see IResource#setReadOnly(boolean)
+ * @deprecated
+ */
+ 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
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResource#setResourceAttributes(org.eclipse.core.resources.ResourceAttributes)
+ */
+ public void setResourceAttributes(ResourceAttributes attributes) throws CoreException {
+ checkAccessibleAndLocal(DEPTH_ZERO);
+ getLocalManager().setResourceAttributes(this, attributes);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#setSessionProperty(QualifiedName, Object)
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#setTeamPrivateMember(boolean)
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see Object#toString()
+ */
+ public String toString() {
+ return getTypeString() + getFullPath().toString();
+ }
+
+ /* (non-Javadoc)
+ * @see IResource#touch(IProgressMonitor)
+ */
+ 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) throws CoreException {
+ 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));
+ 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;
+ }
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceException.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * 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.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. + */ + 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. + */ + 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. + */ + 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceInfo.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,466 @@ +/******************************************************************************* + * 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 + * Oakland Software Incorporated - added getSessionProperties and getPersistentProperties + *******************************************************************************/ +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;
+ }
+
+ 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.
+ *
+ * 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
+ * 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:
+ * 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.
+ */
+ 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.
+ */
+ 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;
+ }
+
+ /**
+ * 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.
+ */
+ 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
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceProxy.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.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()
+ */
+ public long getModificationStamp() {
+ return info.getModificationStamp();
+ }
+
+ public String getName() {
+ return requestor.requestName();
+ }
+
+ public Object getSessionProperty(QualifiedName key) {
+ return info.getSessionProperty(key);
+ }
+
+ public int getType() {
+ return info.getType();
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IResourceProxy#isAccessible()
+ */
+ 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()
+ */
+ public boolean isDerived() {
+ int flags = info.getFlags();
+ return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_DERIVED);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IResourceProxy#isLinked()
+ */
+ public boolean isLinked() {
+ int flags = info.getFlags();
+ return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_LINK);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IResourceProxy#isPhantom()
+ */
+ public boolean isPhantom() {
+ int flags = info.getFlags();
+ return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_PHANTOM);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IResourceProxy#isTeamPrivateMember()
+ */
+ 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()
+ */
+ public boolean isHidden() {
+ int flags = info.getFlags();
+ return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_HIDDEN);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IResourceProxy#requestFullPath()
+ */
+ public IPath requestFullPath() {
+ if (fullPath == null)
+ fullPath = requestor.requestPath();
+ return fullPath;
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IResourceProxy#requestResource()
+ */
+ public IResource requestResource() {
+ if (resource == null)
+ resource = workspace.newResource(requestFullPath(), info.getType());
+ return resource;
+ }
+
+ protected void reset() {
+ fullPath = null;
+ resource = null;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceStatus.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * 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.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()
+ */
+ 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
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,1141 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.internal.resources;
+
+import java.net.URI;
+import org.eclipse.core.filesystem.*;
+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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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.
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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());
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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()) {
+ 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)
+ */
+ 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)
+ */
+ 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() {
+ public boolean visit(IResource resource) {
+ if (resource.isLinked()) {
+ if (isDeep) {
+ //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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourcesCompatibilityHelper.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourcesCompatibilityHelper.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.internal.localstore.HistoryStore2;
+import org.eclipse.core.internal.localstore.IHistoryStore;
+import org.eclipse.core.internal.properties.IPropertyManager;
+import org.eclipse.core.internal.properties.PropertyManager2;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * This is mostly a convenience class for accessing the ResourcesCompatibility class from the
+ * compatibility fragment using reflection.
+ *
+ * See the ResourcesCompatibility class in the compatibility fragment.
+ */
+public class ResourcesCompatibilityHelper {
+ private static final String COMPATIBILITY_CLASS = "org.eclipse.core.internal.resources.ResourcesCompatibility"; //$NON-NLS-1$
+ private static final String CONVERT_HISTORY_STORE = ResourcesPlugin.PI_RESOURCES + ".convertHistory"; //$NON-NLS-1$
+ private static final String CONVERT_PROPERTY_STORE = ResourcesPlugin.PI_RESOURCES + ".convertProperties"; //$NON-NLS-1$
+ private static final String ENABLE_NEW_HISTORY_STORE = ResourcesPlugin.PI_RESOURCES + ".newHistory"; //$NON-NLS-1$
+ private static final String ENABLE_NEW_PROPERTY_STORE = ResourcesPlugin.PI_RESOURCES + ".newProperties"; //$NON-NLS-1$
+
+ /**
+ * Creates a history store. Decides which implementation of history store should be chosen, and whether
+ * conversion from the existing state should be performed by looking at some system properties.
+ */
+ public static IHistoryStore createHistoryStore(IPath location, int limit) {
+ // the default is to use new implementation
+ boolean newImpl = !Boolean.FALSE.toString().equalsIgnoreCase(System.getProperty(ENABLE_NEW_HISTORY_STORE));
+ // the default is to convert existing state to the new implementation
+ boolean convert = !Boolean.FALSE.toString().equalsIgnoreCase(System.getProperty(CONVERT_HISTORY_STORE));
+ try {
+ return createHistoryStore(location, limit, newImpl, convert, true);
+ } catch (ClassNotFoundException e) {
+ // fragment not available
+ } catch (NoSuchMethodException e) {
+ // unlikely
+ if (Workspace.DEBUG)
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // unlikely
+ if (Workspace.DEBUG)
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ // got a runtime exception/error
+ Throwable target = e.getTargetException();
+ if (target instanceof RuntimeException)
+ throw (RuntimeException) target;
+ throw (Error) target;
+ }
+ // default to new version
+ IFileStore store = EFS.getLocalFileSystem().getStore(location);
+ return new HistoryStore2((Workspace) ResourcesPlugin.getWorkspace(), store, limit);
+ }
+
+ public static IHistoryStore createHistoryStore(IPath location, int limit, boolean newImpl, boolean convert, boolean rename) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ Class clazz = Class.forName(COMPATIBILITY_CLASS);
+ Method createMethod = clazz.getDeclaredMethod("createHistoryStore", new Class[] {IPath.class, int.class, boolean.class, boolean.class, boolean.class}); //$NON-NLS-1$
+ return (IHistoryStore) createMethod.invoke(null, new Object[] {location, new Integer(limit), Boolean.valueOf(newImpl), Boolean.valueOf(convert), Boolean.valueOf(rename)});
+ }
+
+ public static IPropertyManager createPropertyManager(boolean newImpl, boolean convert) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ Class clazz = Class.forName(COMPATIBILITY_CLASS);
+ Method createMethod = clazz.getDeclaredMethod("createPropertyManager", new Class[] {boolean.class, boolean.class}); //$NON-NLS-1$
+ return (IPropertyManager) createMethod.invoke(null, new Object[] {Boolean.valueOf(newImpl), Boolean.valueOf(convert)});
+ }
+
+ /**
+ * Creates a property manager. Decides which implementation of property manager should be chosen, and whether
+ * conversion from the existing state should be performed by looking at some system properties.
+ */
+ public static IPropertyManager createPropertyManager() {
+ // the default is to use new implementation
+ boolean newImpl = !Boolean.FALSE.toString().equalsIgnoreCase(System.getProperty(ENABLE_NEW_PROPERTY_STORE));
+ // the default is to convert existing state to the new implementation
+ boolean convert = !Boolean.FALSE.toString().equalsIgnoreCase(System.getProperty(CONVERT_PROPERTY_STORE));
+ try {
+ return createPropertyManager(newImpl, convert);
+ } catch (ClassNotFoundException e) {
+ // fragment not available
+ } catch (NoSuchMethodException e) {
+ // unlikely
+ if (Workspace.DEBUG)
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // unlikely
+ if (Workspace.DEBUG)
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ // got a runtime exception/error
+ Throwable target = e.getTargetException();
+ if (target instanceof RuntimeException)
+ throw (RuntimeException) target;
+ throw (Error) target;
+ }
+ // default to new version
+ return new PropertyManager2((Workspace) ResourcesPlugin.getWorkspace());
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/RootInfo.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 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.
+ */
+ 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.
+ */
+ public void incrementSyncInfoGenerationCount() {
+ // do nothing
+ }
+
+ /**
+ * Sets the property store associated with this info. The value may be null.
+ */
+ public void setPropertyStore(Object value) {
+ propertyStore = value;
+ }
+
+ /**
+ * Overrides parent's behaviour since sync information is not
+ * stored on the workspace root.
+ */
+ public void setSyncInfo(QualifiedName id, byte[] value) {
+ // do nothing
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Rules.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,201 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 - Initial API and implementation
+ *******************************************************************************/
+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.
+ */
+ public ISchedulingRule buildRule() {
+ //team hook currently cannot change this rule
+ return root;
+ }
+
+ /**
+ * Obtains the scheduling rule from the appropriate factories for a copy operation.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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 = (IResourceRuleFactory) 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)
+ */
+ 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.
+ */
+ 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 marker change operation.
+ */
+ public ISchedulingRule markerRule(IResource resource) {
+ //team hook currently cannot change this rule
+ return null;
+ }
+
+ /**
+ * Obtains the scheduling rule from the appropriate factory for a modify operation.
+ */
+ 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.
+ */
+ 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(deleteRule(source), createRule(destination));
+ return factoryFor(source).moveRule(source, destination);
+ }
+
+ /**
+ * Obtains the scheduling rule from the appropriate factory for a refresh operation.
+ */
+ 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.
+ */
+ 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 (ISchedulingRule) rules.iterator().next();
+ ISchedulingRule[] ruleArray = (ISchedulingRule[]) rules.toArray(new ISchedulingRule[rules.size()]);
+ return new MultiRule(ruleArray);
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SafeFileTable.java Mon Jun 01 10:16:20 2009 -0500
@@ -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.resources;
+
+import java.io.*;
+import java.util.Properties;
+import java.util.Set;
+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 = (String[]) 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$
+ } finally {
+ output.close();
+ }
+ } 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveContext.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.internal.resources;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+
+public class SaveContext implements ISaveContext {
+ protected Plugin plugin;
+ protected int kind;
+ protected boolean needDelta;
+ protected boolean needSaveNumber;
+ protected SafeFileTable fileTable;
+ protected int previousSaveNumber;
+ protected IProject project;
+
+ protected SaveContext(Plugin plugin, int kind, IProject project) throws CoreException {
+ this.plugin = plugin;
+ this.kind = kind;
+ this.project = project;
+ needDelta = false;
+ needSaveNumber = false;
+ String pluginId = plugin.getBundle().getSymbolicName();
+ fileTable = new SafeFileTable(pluginId);
+ previousSaveNumber = getWorkspace().getSaveManager().getSaveNumber(pluginId);
+ }
+
+ public void commit() throws CoreException {
+ if (needSaveNumber) {
+ String pluginId = plugin.getBundle().getSymbolicName();
+ IPath oldLocation = getWorkspace().getMetaArea().getSafeTableLocationFor(pluginId);
+ getWorkspace().getSaveManager().setSaveNumber(pluginId, getSaveNumber());
+ fileTable.setLocation(getWorkspace().getMetaArea().getSafeTableLocationFor(pluginId));
+ fileTable.save();
+ oldLocation.toFile().delete();
+ }
+ }
+
+ /**
+ * @see ISaveContext
+ */
+ public IPath[] getFiles() {
+ return getFileTable().getFiles();
+ }
+
+ protected SafeFileTable getFileTable() {
+ return fileTable;
+ }
+
+ /**
+ * @see ISaveContext
+ */
+ public int getKind() {
+ return kind;
+ }
+
+ /**
+ * @see ISaveContext
+ */
+ public Plugin getPlugin() {
+ return plugin;
+ }
+
+ /**
+ * @see ISaveContext
+ */
+ public int getPreviousSaveNumber() {
+ return previousSaveNumber;
+ }
+
+ /**
+ * @see ISaveContext
+ */
+ public IProject getProject() {
+ return project;
+ }
+
+ /**
+ * @see ISaveContext
+ */
+ 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
+ */
+ public IPath lookup(IPath file) {
+ return getFileTable().lookup(file);
+ }
+
+ /**
+ * @see ISaveContext
+ */
+ public void map(IPath file, IPath location) {
+ getFileTable().map(file, location);
+ }
+
+ /**
+ * @see ISaveContext
+ */
+ public void needDelta() {
+ needDelta = true;
+ }
+
+ /**
+ * @see ISaveContext
+ */
+ public void needSaveNumber() {
+ needSaveNumber = true;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,1758 @@
+/*******************************************************************************
+ * 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.resources;
+
+import java.io.*;
+import java.util.*;
+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 {
+ 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 Properties 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;
+
+ /**
+ * Plugins that participate on a workspace save. Maps (Plugin -> 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;
+ 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.snapshotJob = new DelayedSnapshotJob(this);
+ snapshotRequested = false;
+ saveParticipants = Collections.synchronizedMap(new HashMap(10));
+ }
+
+ public ISavedState addParticipant(Plugin plugin, ISaveParticipant participant) throws CoreException {
+ // If the plugin was already registered as a save participant we return null
+ if (saveParticipants.put(plugin, participant) != null)
+ return null;
+ String id = plugin.getBundle().getSymbolicName();
+ SavedState state = (SavedState) savedStates.get(id);
+ if (state != null) {
+ if (isDeltaCleared(id)) {
+ // this plugin was marked not to receive deltas
+ state.forgetTrees();
+ removeClearDeltaMarks(id);
+ } 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(id) > 0)
+ return new SavedState(workspace, id, 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 = (Map.Entry) it.next();
+ Plugin plugin = (Plugin) entry.getKey();
+ final ISaveParticipant participant = (ISaveParticipant) saveParticipants.get(plugin);
+ //save participants can be removed concurrently
+ if (participant == null) {
+ monitor.worked(1);
+ continue;
+ }
+ final SaveContext context = (SaveContext) entry.getValue();
+ /* Be extra careful when calling lifecycle method on arbitrary plugin */
+ ISafeRunnable code = new ISafeRunnable() {
+
+ 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();
+ }
+
+ 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(backup);
+ } catch (CoreException e) {
+ Policy.log(e.getStatus());
+ backup.toFile().delete();
+ return;
+ }
+ if (location.toFile().exists() && !location.toFile().delete())
+ return;
+ try {
+ saveMasterTable(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 = ((Plugin) i.next()).getBundle().getSymbolicName();
+ 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() throws CoreException {
+ //collect trees we're interested in
+
+ //trees for plugin saved states
+ ArrayList trees = new ArrayList();
+ synchronized (savedStates) {
+ for (Iterator i = savedStates.values().iterator(); i.hasNext();) {
+ SavedState state = (SavedState) 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 = (BuilderPersistentInfo) 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();)
+ ((SaveContext) 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(Plugin[] plugins, int kind, IProject project) {
+ HashMap result = new HashMap(plugins.length);
+ for (int i = 0; i < plugins.length; i++) {
+ Plugin plugin = plugins[i];
+ try {
+ SaveContext context = new SaveContext(plugin, kind, project);
+ result.put(plugin, 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 = (SavedState) i.next();
+ if (state.oldTree != null)
+ result.put(state.pluginId, state.oldTree);
+ }
+ }
+ for (Iterator i = contexts.values().iterator(); i.hasNext();) {
+ SaveContext context = (SaveContext) i.next();
+ if (!context.isDeltaNeeded())
+ continue;
+ String pluginId = context.getPlugin().getBundle().getSymbolicName();
+ 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();)
+ ((SavedState) i.next()).forgetTrees();
+ }
+ } else {
+ SavedState state = (SavedState) 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 Plugin[] getSaveParticipantPlugins() {
+ synchronized (saveParticipants) {
+ return (Plugin[]) saveParticipants.keySet().toArray(new Plugin[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)
+ System.out.println(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 :
+ System.out.println(DEBUG_FULL_SAVE + DEBUG_START);
+ break;
+ case ISaveContext.SNAPSHOT :
+ System.out.println(DEBUG_SNAPSHOT + DEBUG_START);
+ break;
+ case ISaveContext.PROJECT_SAVE :
+ System.out.println(DEBUG_PROJECT_SAVE + project.getFullPath() + DEBUG_START);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Initializes the snapshot mechanism for this workspace.
+ */
+ protected void initSnap(IProgressMonitor monitor) throws CoreException {
+ //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 file, if any
+ IPath snapPath = workspace.getMetaArea().getSnapshotLocationFor(workspace.getRoot());
+ java.io.File file = snapPath.toFile();
+ if (file.exists())
+ file.delete();
+ if (file.exists()) {
+ String message = Messages.resources_snapInit;
+ throw new ResourceException(IResourceStatus.FAILED_DELETE_METADATA, null, message, null);
+ }
+ }
+
+ 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)
+ */
+ 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;
+ }
+
+ /**
+ * 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 = ((Plugin) i.next()).getBundle().getSymbolicName();
+ 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 = (String) 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(Plugin plugin) {
+ saveParticipants.remove(plugin);
+ }
+
+ 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() {
+ 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);
+ }
+ }
+
+ 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)
+ System.out.println("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)
+ System.out.println("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)
+ System.out.println("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)
+ System.out.println("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) {
+ System.out.println("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) {
+ System.out.println("Restore Markers for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ protected void restoreMasterTable() throws CoreException {
+ long start = System.currentTimeMillis();
+ masterTable = new Properties();
+ 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)
+ System.out.println("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)
+ System.out.println("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)
+ System.out.println("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) {
+ //close the project
+ project.internalClose();
+ throw failure;
+ }
+ if (Policy.DEBUG_RESTORE_METAINFO)
+ System.out.println("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) throws CoreException {
+ 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 the snapshot file doesn't exist, there was no crash.
+ // Just initialize the snapshot file and return.
+ if (!localFile.exists()) {
+ 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)
+ System.out.println("Restore snapshots for workspace: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * 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) {
+ System.out.println("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) {
+ System.out.println("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) {
+ System.out.println("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) {
+ System.out.println("Restore Tree for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return true;
+ }
+
+ public IStatus save(int kind, Project project, IProgressMonitor monitor) throws CoreException {
+ monitor = Policy.monitorFor(monitor);
+ 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(getSaveParticipantPlugins(), 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);
+ //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();
+ workspace.getFileSystemManager().getHistoryStore().clean(Policy.subMonitorFor(monitor, 1));
+ // 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();
+ 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();
+ 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() throws CoreException {
+ saveMasterTable(workspace.getMetaArea().getSafeTableLocationFor(ResourcesPlugin.PI_RESOURCES));
+ }
+
+ protected void saveMasterTable(IPath location) throws CoreException {
+ long start = System.currentTimeMillis();
+ java.io.File target = location.toFile();
+ try {
+ SafeChunkyOutputStream output = new SafeChunkyOutputStream(target);
+ try {
+ masterTable.store(output, "master table"); //$NON-NLS-1$
+ output.succeed();
+ } finally {
+ output.close();
+ }
+ } catch (IOException e) {
+ String message = Messages.resources_exSaveMaster;
+ throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e);
+ }
+ if (Policy.DEBUG_SAVE_MASTERTABLE)
+ System.out.println("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)
+ System.out.println("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)
+ System.out.println("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)
+ System.out.println("Save metainfo for " + project.getFullPath() + ": " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ return Status.OK_STATUS;
+ }
+
+ /**
+ * 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);
+ } finally {
+ output.close();
+ }
+ } 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)
+ System.out.println("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
+ */
+ public void shareStrings(StringPool pool) {
+ lastSnap.shareStrings(pool);
+ }
+
+ 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()) {
+ if (snapshotJob.getState() == Job.NONE)
+ snapshotJob.schedule();
+ else
+ snapshotJob.wakeUp();
+ } else {
+ if (hasTreeChanges) {
+ operationCount++;
+ if (snapshotJob.getState() == Job.NONE) {
+ if (Policy.DEBUG_SAVE)
+ System.out.println("Scheduling workspace snapshot"); //$NON-NLS-1$
+ 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();
+ } finally {
+ out.close();
+ }
+ } 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)
+ System.out.println("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 = (List) 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 = (List) 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;
+ }
+
+ 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()));
+ }
+
+ /**
+ * 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) {
+ if (o1 != null)
+ try {
+ o1.close();
+ } catch (IOException e2) {
+ // ignore
+ }
+ 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() {
+ 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)
+ System.out.println("Save Markers for " + root.getFullPath() + ": " + saveTimes[0] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ if (Policy.DEBUG_SAVE_SYNCINFO)
+ System.out.println("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);
+ } 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() {
+ 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)
+ System.out.println("Snap Markers for " + root.getFullPath() + ": " + snapTimes[0] + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ if (Policy.DEBUG_SAVE_SYNCINFO)
+ System.out.println("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();
+ } 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
+ */
+ protected void writeBuilderPersistentInfo(DataOutputStream output, List builders, List trees, 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 = (BuilderPersistentInfo) 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());
+ ElementTree last = info.getLastBuiltTree();
+ //it is not unusual for a builder to have no last built tree (for example after a clean)
+ if (last == null)
+ last = workspace.getElementTree();
+ trees.add(last);
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * @see IElementInfoFlattener#writeElement(IPath, Object, DataOutput)
+ */
+ 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);
+ }
+
+ 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 (Iterator i = statesToSave.entrySet().iterator(); i.hasNext();) {
+ Map.Entry entry = (Map.Entry) i.next();
+ String pluginId = (String) entry.getKey();
+ output.writeUTF(pluginId);
+ trees.add(entry.getValue()); // tree
+ updateDeltaExpiration(pluginId);
+ }
+ monitor.worked(Policy.totalWork * 10 / 100);
+
+ // add builders' trees
+ IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
+ List builders = new ArrayList(projects.length * 2);
+ for (int i = 0; i < projects.length; i++) {
+ IProject project = projects[i];
+ if (project.isOpen()) {
+ ArrayList infos = workspace.getBuildManager().createBuildersPersistentInfo(project);
+ if (infos != null)
+ builders.addAll(infos);
+ }
+ }
+ writeBuilderPersistentInfo(output, builders, trees, Policy.subMonitorFor(monitor, Policy.totalWork * 10 / 100));
+
+ // add the current tree in the list as the last element
+ trees.add(current);
+
+ /* save the forest! */
+ ElementTreeWriter writer = new ElementTreeWriter(this);
+ ElementTree[] treesToSave = (ElementTree[]) trees.toArray(new ElementTree[trees.size()]);
+ writer.writeDeltaChain(treesToSave, Path.ROOT, ElementTreeWriter.D_INFINITE, output, ResourceComparator.getSaveComparator());
+ monitor.worked(Policy.totalWork * 50 / 100);
+ } finally {
+ if (!wasImmutable)
+ workspace.newWorkingTree();
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * Attempts to save all the trees for this project (the current tree
+ * plus a tree for each builder with a previously built state). Throws
+ * an IOException if anything went wrong during save. Attempts to close
+ * the provided stream at all costs.
+ */
+ protected void writeTree(Project project, DataOutputStream output, IProgressMonitor monitor) throws IOException, CoreException {
+ monitor = Policy.monitorFor(monitor);
+ try {
+ monitor.beginTask("", 10); //$NON-NLS-1$
+ boolean wasImmutable = false;
+ try {
+ /**
+ * Obtain a list of BuilderPersistentInfo.
+ * This list includes builders that have never been instantiated
+ * but already had a last built state.
+ */
+ ArrayList builderInfos = workspace.getBuildManager().createBuildersPersistentInfo(project);
+ if (builderInfos == null)
+ builderInfos = new ArrayList(5);
+ List trees = new ArrayList(builderInfos.size() + 1);
+ monitor.worked(1);
+
+ /* Make sure the most recent tree is in the array */
+ ElementTree current = workspace.getElementTree();
+ wasImmutable = current.isImmutable();
+ current.immutable();
+
+ /* add the tree for each builder to the array */
+ writeBuilderPersistentInfo(output, builderInfos, trees, Policy.subMonitorFor(monitor, 1));
+ trees.add(current);
+
+ /* save the forest! */
+ ElementTreeWriter writer = new ElementTreeWriter(this);
+ ElementTree[] treesToSave = (ElementTree[]) trees.toArray(new ElementTree[trees.size()]);
+ writer.writeDeltaChain(treesToSave, project.getFullPath(), ElementTreeWriter.D_INFINITE, output, ResourceComparator.getSaveComparator());
+ monitor.worked(8);
+ } finally {
+ if (output != null)
+ output.close();
+ 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());
+ try {
+ DataOutputStream output = new DataOutputStream(safe);
+ output.writeInt(ICoreConstants.WORKSPACE_TREE_VERSION_2);
+ writeTree(project, output, null);
+ } finally {
+ safe.close();
+ }
+ } 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)
+ System.out.println("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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SavedState.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.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);
+ }
+
+ 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;
+ }
+
+ public IPath lookup(IPath file) {
+ return getFileTable().lookup(file);
+ }
+
+ public IPath[] getFiles() {
+ return getFileTable().getFiles();
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * 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 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_2.java Mon Jun 01 10:16:20 2009 -0500
@@ -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.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[]
+ *
+ */
+ 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 = (QualifiedName) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoReader_3.java Mon Jun 01 10:16:20 2009 -0500
@@ -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.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[]
+ *
+ */
+ 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 = (QualifiedName) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoSnapReader_3.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * 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.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[]
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SyncInfoWriter.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * 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.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 = (QualifiedName) 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 (Iterator i = table.entrySet().iterator(); i.hasNext();) {
+ Map.Entry entry = (Map.Entry) i.next();
+ QualifiedName name = (QualifiedName) 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 (Iterator i = table.entrySet().iterator(); i.hasNext();) {
+ Map.Entry entry = (Map.Entry) i.next();
+ QualifiedName name = (QualifiedName) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Synchronizer.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,265 @@
+/*******************************************************************************
+ * 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.*;
+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.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)
+ */
+ 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)
+ */
+ public void add(QualifiedName partner) {
+ Assert.isLegal(partner != null);
+ registry.add(partner);
+ }
+
+ /**
+ * @see ISynchronizer#flushSyncInfo(QualifiedName, IResource, int)
+ */
+ 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() {
+ public void run(IProgressMonitor monitor) throws CoreException {
+ IResourceVisitor visitor = new IResourceVisitor() {
+ 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()
+ */
+ public QualifiedName[] getPartners() {
+ return (QualifiedName[]) registry.toArray(new QualifiedName[registry.size()]);
+ }
+
+ /**
+ * For use by the serialization code.
+ */
+ protected Set getRegistry() {
+ return registry;
+ }
+
+ /**
+ * @see ISynchronizer#getSyncInfo(QualifiedName, IResource)
+ */
+ 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)
+ */
+ 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[])
+ */
+ public void setSyncInfo(QualifiedName partner, IResource resource, byte[] info) throws CoreException {
+ Assert.isLegal(partner != null);
+ Assert.isLegal(resource != null);
+ try {
+ workspace.prepareOperation(resource, 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(resource, false, null);
+ }
+ }
+
+ public void snapSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException {
+ writer.snapSyncInfo(info, requestor, output);
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/TestingSupport.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,317 @@
+/*******************************************************************************
+ * 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.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 {
+ public boolean contains(ISchedulingRule rule) {
+ return (rule instanceof IResource) || rule.getClass().equals(NotifyRule.class);
+ }
+
+ 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.
+ * 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)
+ operationCanceled = hasBuildChanges = 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;
+ }
+
+ public void shutdown(IProgressMonitor monitor) {
+ // do nothing
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,2123 @@
+/*******************************************************************************
+ * 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
+ * Red Hat Incorporated - loadProjectDescription(InputStream)
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.*;
+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.refresh.RefreshManager;
+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.
+ *
+ * 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$ + // whether the resources plugin is in debug mode. + public static boolean DEBUG = false; + + /** + * 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 IProject[] 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 HashSet lifecycleListeners = new HashSet(10); + 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 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 hand both concurrent + * reads and concurrent writes, write access to the tree is governed + * by {@link WorkManager}. + */ + protected 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; + + /** + * 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) { + boolean result = clearChildren(root); + try { + if (root.exists()) + result &= root.delete(); + } catch (Exception e) { + result = false; + } + return result; + } + + /** + * Deletes all the files and directories from the given root down, except for + * the root itself. + * 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 clearChildren(java.io.File root) { + boolean result = true; + if (root.isDirectory()) { + String[] list = root.list(); + // for some unknown reason, list() can return null. + // Just skip the children If it does. + if (list != null) + for (int i = 0; i < list.length; i++) + result &= clear(new java.io.File(root, list[i])); + } + return result; + } + + 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.add(listener); + } + + /* (non-Javadoc) + * @see IWorkspace#addResourceChangeListener(IResourceChangeListener) + */ + public void addResourceChangeListener(IResourceChangeListener listener) { + notificationManager.addListener(listener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE); + } + + /* (non-Javadoc) + * @see IWorkspace#addResourceChangeListener(IResourceChangeListener, int) + */ + public void addResourceChangeListener(IResourceChangeListener listener, int eventMask) { + notificationManager.addListener(listener, eventMask); + } + + /* (non-Javadoc) + * @see IWorkspace#addSaveParticipant(Plugin, ISaveParticipant) + */ + 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, 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 (Iterator it = lifecycleListeners.iterator(); it.hasNext();) { + ILifecycleListener listener = (ILifecycleListener) it.next(); + listener.handleEvent(event); + } + } + + public void broadcastPostChange() { + ResourceChangeEvent event = new ResourceChangeEvent(this, IResourceChangeEvent.POST_CHANGE, 0, null); + notificationManager.broadcastChanges(tree, event, true); + } + + /* (non-Javadoc) + * @see IWorkspace#build(int, IProgressMonitor) + */ + public void build(int trigger, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + final ISchedulingRule rule = getRuleFactory().buildRule(); + try { + monitor.beginTask("", Policy.opWork); //$NON-NLS-1$ + try { + prepareOperation(rule, monitor); + beginOperation(true); + aboutToBuild(this, trigger); + IStatus result; + try { + result = getBuildManager().build(trigger, Policy.subMonitorFor(monitor, Policy.opWork)); + } finally { + //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(); + endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } + } finally { + monitor.done(); + } + } + + /** + * Returns whether creating executable extensions is acceptable + * at this point in time. In particular, returnsfalse
+ * 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$
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#checkpoint(boolean)
+ */
+ 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, ornull
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);
+ 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 ProjectOrder 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() { + public int compare(Object x, Object y) { + IProject px = (IProject) x; + IProject py = (IProject) y; + return py.getName().compareTo(px.getName()); + } + }); + IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); + // ListIWorkspace.computeProjectOrder
, which
+ * produces a more usable result when there are cycles in project reference
+ * graph.
+ */
+ 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
+ * The project build order is based on information specified in the workspace
+ * description. The projects are built in the order specified by
+ * IWorkspaceDescription.getBuildOrder
; closed or non-existent
+ * projects are ignored and not included in the result. If
+ * IWorkspaceDescription.getBuildOrder
is non-null, the default
+ * build order is used; again, only open projects are included in the result.
+ *
+ * The returned value is cached in the buildOrder
field.
+ *
IWorkspace.build
.
+ * @see IWorkspace#build(int, IProgressMonitor)
+ * @see IWorkspaceDescription#getBuildOrder()
+ * @since 2.1
+ */
+ public IProject[] getBuildOrder() {
+ if (buildOrder != null) {
+ // return previously-computed and cached project build order
+ return buildOrder;
+ }
+ // see if a particular build order is specified
+ String[] order = description.getBuildOrder(false);
+ if (order != null) {
+ // convert from project names to project handles
+ // and eliminate non-existent and closed projects
+ List projectList = new ArrayList(order.length);
+ for (int i = 0; i < order.length; i++) {
+ IProject project = getRoot().getProject(order[i]);
+ if (project.isAccessible()) {
+ projectList.add(project);
+ }
+ }
+ buildOrder = new IProject[projectList.size()];
+ projectList.toArray(buildOrder);
+ } else {
+ // use default project build order
+ // computed for all accessible projects in workspace
+ buildOrder = computeFullProjectOrder().projects;
+ }
+ return buildOrder;
+ }
+
+ public CharsetManager getCharsetManager() {
+ return charsetManager;
+ }
+
+ public ContentDescriptionManager getContentDescriptionManager() {
+ return contentDescriptionManager;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#getDanglingReferences()
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#getDescription()
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#getNatureDescriptor(String)
+ */
+ public IProjectNatureDescriptor getNatureDescriptor(String natureId) {
+ return natureManager.getNatureDescriptor(natureId);
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#getNatureDescriptors()
+ */
+ public IProjectNatureDescriptor[] getNatureDescriptors() {
+ return natureManager.getNatureDescriptors();
+ }
+
+ /**
+ * Returns the nature manager for this workspace.
+ */
+ public NatureManager getNatureManager() {
+ return natureManager;
+ }
+
+ public NotificationManager getNotificationManager() {
+ return notificationManager;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#getPathVariableManager()
+ */
+ 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;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#getRoot()
+ */
+ public IWorkspaceRoot getRoot() {
+ return defaultRoot;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#getRuleFactory()
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#getSynchronizer()
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#isAutoBuilding()
+ */
+ public boolean isAutoBuilding() {
+ return description.isAutoBuilding();
+ }
+
+ public boolean isOpen() {
+ return openFlag;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#isTreeLocked()
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#loadProjectDescription(InputStream)
+ * @since 3.1
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#loadProjectDescription(IPath)
+ * @since 2.0
+ */
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#move(IResource[], IPath, boolean, IProgressMonitor)
+ */
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#move(IResource[], IPath, int, IProgressMonitor)
+ */
+ 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 = (IResource[]) 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);
+ 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;
+ }
+
+ /* (non-Javadoc)
+ * @see IWorkspace#newProjectDescription(String)
+ */
+ 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, ornull
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:
+ * IFile#appendContents
IFile#setContents(InputStream, boolean, boolean, IProgressMonitor)
IFile#setContents(IFileState, boolean, boolean, IProgressMonitor)
WorkspaceDescription
+ * but instead of changing/obtaining values from its internal state, it
+ * changes/obtains properties in/from the workspace plug-in's preferences.
+ *
+ * Obs.: for performance reasons, some frequently called acessor 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 (String[]) 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.setFileStateLongevity(source.getFileStateLongevity());
+ target.setMaxBuildIterations(source.getMaxBuildIterations());
+ 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.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() {
+ public void propertyChange(PropertyChangeEvent event) {
+ synchronizeWithPreferences(event.getProperty());
+ }
+ });
+ }
+
+ 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()
+ */
+ 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)
+ */
+ 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)
+ */
+ public void setAutoBuilding(boolean value) {
+ preferences.setValue(ResourcesPlugin.PREF_AUTO_BUILDING, value);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IWorkspaceDescription#setBuildOrder(String[])
+ */
+ public void setBuildOrder(String[] value) {
+ preferences.setValue(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER, value == null);
+ preferences.setValue(ResourcesPlugin.PREF_BUILD_ORDER, convertStringArraytoString(value));
+ }
+
+ public void setDeltaExpiration(long value) {
+ preferences.setValue(PreferenceInitializer.PREF_DELTA_EXPIRATION, value);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IWorkspaceDescription#setFileStateLongevity(long)
+ */
+ public void setFileStateLongevity(long time) {
+ preferences.setValue(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, time);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxBuildIterations(int)
+ */
+ public void setMaxBuildIterations(int number) {
+ preferences.setValue(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS, number);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxFileStates(int)
+ */
+ public void setMaxFileStates(int number) {
+ preferences.setValue(ResourcesPlugin.PREF_MAX_FILE_STATES, number);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IWorkspaceDescription#setMaxFileStateSize(long)
+ */
+ public void setMaxFileStateSize(long size) {
+ preferences.setValue(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, size);
+ }
+
+ public void setOperationsPerSnapshot(int value) {
+ preferences.setValue(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT, value);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IWorkspaceDescription#setSnapshotInterval(long)
+ */
+ 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_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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceRoot.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,319 @@
+/*******************************************************************************
+ * 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.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.resources.*;
+import org.eclipse.core.runtime.*;
+
+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);
+ }
+
+ /**
+ * @see IWorkspaceRoot#delete(boolean, boolean, IProgressMonitor)
+ */
+ 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);
+ }
+
+ /**
+ * @see org.eclipse.core.internal.resources.Resource#delete(boolean, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void delete(boolean force, IProgressMonitor monitor) throws CoreException {
+ int updateFlags = force ? IResource.FORCE : IResource.NONE;
+ delete(updateFlags, monitor);
+ }
+
+ /**
+ * @see org.eclipse.core.internal.resources.Resource#exists(int, boolean)
+ */
+ public boolean exists(int flags, boolean checkType) {
+ return true;
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IWorkspaceRoot#findContainersForLocation(org.eclipse.core.runtime.IPath)
+ */
+ public IContainer[] findContainersForLocation(IPath location) {
+ return findContainersForLocationURI(URIUtil.toURI(location.makeAbsolute()));
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IWorkspaceRoot#findContainersForLocationURI(java.net.URI)
+ */
+ public IContainer[] findContainersForLocationURI(URI location) {
+ if (!location.isAbsolute())
+ throw new IllegalArgumentException();
+ return (IContainer[]) getLocalManager().allResourcesFor(location, false);
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IWorkspaceRoot#findFilesForLocation(org.eclipse.core.runtime.IPath)
+ */
+ public IFile[] findFilesForLocation(IPath location) {
+ return findFilesForLocationURI(URIUtil.toURI(location.makeAbsolute()));
+ }
+
+ /**
+ * @see org.eclipse.core.resources.IWorkspaceRoot#findFilesForLocationURI(java.net.URI)
+ */
+ public IFile[] findFilesForLocationURI(URI location) {
+ if (!location.isAbsolute())
+ throw new IllegalArgumentException();
+ return (IFile[]) getLocalManager().allResourcesFor(location, true);
+ }
+
+ /**
+ * @see IWorkspaceRoot#getContainerForLocation(IPath)
+ */
+ public IContainer getContainerForLocation(IPath location) {
+ return getLocalManager().containerForLocation(location);
+ }
+
+ /**
+ * @see IContainer#getDefaultCharset(boolean)
+ */
+ 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;
+ }
+
+ /**
+ * @see IWorkspaceRoot#getFileForLocation(IPath)
+ */
+ public IFile getFileForLocation(IPath location) {
+ return getLocalManager().fileForLocation(location);
+ }
+
+ /**
+ * @see IResource#getLocalTimeStamp()
+ */
+ public long getLocalTimeStamp() {
+ return IResource.NULL_STAMP;
+ }
+
+ /**
+ * @see IResource#getLocation()
+ */
+ public IPath getLocation() {
+ return workspaceLocation;
+ }
+
+ /**
+ * @see IResource#getName()
+ */
+ public String getName() {
+ return ""; //$NON-NLS-1$
+ }
+
+ /**
+ * @see IResource#getParent()
+ */
+ public IContainer getParent() {
+ return null;
+ }
+
+ /**
+ * @see IResource#getProject()
+ */
+ public IProject getProject() {
+ return null;
+ }
+
+ /**
+ * @see IWorkspaceRoot#getProject(String)
+ */
+ public IProject getProject(String name) {
+ //first check our project cache
+ Project result = (Project) 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 = (Project) projectTable.get(canonicalName);
+ if (result != null)
+ return result;
+ result = new Project(projectPath, workspace);
+ projectTable.put(canonicalName, result);
+ }
+ return result;
+ }
+
+ /**
+ * @see IResource#getProjectRelativePath()
+ */
+ public IPath getProjectRelativePath() {
+ return Path.EMPTY;
+ }
+
+ /**
+ * @see IWorkspaceRoot#getProjects()
+ */
+ public IProject[] getProjects() {
+ return getProjects(IResource.NONE);
+ }
+
+ /**
+ * @see IWorkspaceRoot#getProjects(int)
+ */
+ public IProject[] getProjects(int memberFlags) {
+ IResource[] roots = getChildren(memberFlags);
+ IProject[] result = new IProject[roots.length];
+ System.arraycopy(roots, 0, result, 0, roots.length);
+ return result;
+ }
+
+ /**
+ * @see IResource#getType()
+ */
+ public int getType() {
+ return IResource.ROOT;
+ }
+
+ 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);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.resources.Resource#isDerived(int)
+ */
+ public boolean isDerived(int options) {
+ return false;//the root is never derived
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.resources.Resource#isHidden()
+ */
+ public boolean isHidden() {
+ return false;//the root is never hidden
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.resources.Resource#isLinked(int)
+ */
+ public boolean isLinked(int options) {
+ return false;//the root is never linked
+ }
+
+ /**
+ * @see IResource#isLocal(int)
+ * @deprecated
+ */
+ public boolean isLocal(int depth) {
+ // the flags parameter is ignored for the workspace root so pass anything
+ return isLocal(-1, depth);
+ }
+
+ /**
+ * @see IResource#isLocal(int)
+ * @deprecated
+ */
+ 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;
+ }
+
+ /**
+ * @see IResource#isPhantom()
+ */
+ public boolean isPhantom() {
+ return false;
+ }
+
+ /**
+ * @see IContainer#setDefaultCharset(String)
+ * @deprecated Replaced by {@link #setDefaultCharset(String, IProgressMonitor)} which
+ * is a workspace operation and reports changes in resource deltas.
+ */
+ 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);
+ }
+
+ public void setHidden(boolean isHidden) {
+ //workspace root cannot be set hidden
+ }
+
+ /**
+ * @see IResource#setLocalTimeStamp(long)
+ */
+ 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
+ * @see IResource#setReadOnly(boolean)
+ */
+ public void setReadOnly(boolean readonly) {
+ //can't set the root read only
+ }
+
+ /**
+ * @see IResource#touch(IProgressMonitor)
+ */
+ public void touch(IProgressMonitor monitor) {
+ // do nothing for the workspace root
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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 - Initial API and implementation
+ *******************************************************************************/
+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 {
+ /**
+ * Returns the tree reader associated with the given tree version number
+ */
+ public static WorkspaceTreeReader getReader(Workspace workspace, int version) throws CoreException {
+ switch (version) {
+ case ICoreConstants.WORKSPACE_TREE_VERSION_1 :
+ return new WorkspaceTreeReader_1(workspace);
+ case ICoreConstants.WORKSPACE_TREE_VERSION_2 :
+ return new WorkspaceTreeReader_2(workspace);
+ 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 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_1.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,260 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.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 = (BuilderPersistentInfo) 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 = (SavedState) 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();
+ }
+ }
+
+ 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();
+ }
+ }
+
+ 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();
+ }
+ }
+
+ 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());
+ ElementTree[] trees = treeReader.readDeltaChain(input);
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceTreeReader_2.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * 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.io.DataInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+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.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * 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.
+ */
+public class WorkspaceTreeReader_2 extends WorkspaceTreeReader_1 {
+
+ public WorkspaceTreeReader_2(Workspace workspace) {
+ super(workspace);
+ }
+
+ protected int getVersion() {
+ return ICoreConstants.WORKSPACE_TREE_VERSION_2;
+ }
+
+ /*
+ * overwritten from WorkspaceTreeReader_1
+ */
+ 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();
+ }
+ }
+
+ /*
+ * overwritten from WorkspaceTreeReader_1
+ */
+ 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 in the builder infos */
+ List infos = new ArrayList(5);
+ readBuildersPersistentInfo(project, input, infos, Policy.subMonitorFor(monitor, 1));
+
+ /* read and link the trees */
+ ElementTree[] trees = readTrees(project.getFullPath(), input, Policy.subMonitorFor(monitor, 8));
+
+ /* map builder names to trees */
+ linkBuildersToTrees(infos, trees, 0, Policy.subMonitorFor(monitor, 1));
+
+ } catch (IOException e) {
+ message = Messages.resources_readProjectTree;
+ throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, message, e);
+ } finally {
+ monitor.done();
+ }
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/XMLWriter.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * 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.io.*;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * A simple XML writer.
+ */
+public class XMLWriter extends PrintWriter {
+ protected int tab;
+
+ /* constants */
+ protected static final String XML_VERSION = ""; //$NON-NLS-1$
+
+ public XMLWriter(OutputStream output) throws UnsupportedEncodingException {
+ super(new OutputStreamWriter(output, "UTF8")); //$NON-NLS-1$
+ tab = 0;
+ println(XML_VERSION);
+ }
+
+ public void endTag(String name) {
+ tab--;
+ printTag('/' + name, null);
+ }
+
+ 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 (Iterator it = parameters.keySet().iterator(); it.hasNext();) {
+ sb.append(" "); //$NON-NLS-1$
+ String key = (String) it.next();
+ sb.append(key);
+ sb.append("=\""); //$NON-NLS-1$
+ sb.append(getEscaped(String.valueOf(parameters.get(key))));
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ChangeDescription.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * 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.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 = (IResource) 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 (IResource[]) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderDescriptor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.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));
+ }
+
+ public String[] getExtendedModels() {
+ return extendedModels;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ 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 (IResource[]) result.toArray(new IResource[result.size()]);
+ }
+
+ 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 = (String[]) extendsList.toArray(new String[extendsList.size()]);
+ }
+
+ 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 (ResourceTraversal[]) result.toArray(new ResourceTraversal[result.size()]);
+ }
+
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ModelProviderManager.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.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 (IModelProviderDescriptor) descriptors.get(id);
+ }
+
+ public IModelProviderDescriptor[] getDescriptors() {
+ lazyInitialize();
+ return (IModelProviderDescriptor[]) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ProposedResourceDelta.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * 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.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)
+ */
+ 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)
+ */
+ 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)
+ */
+ public void accept(IResourceDeltaVisitor visitor, int memberFlags) throws CoreException {
+ if (!visitor.visit(this))
+ return;
+ for (Iterator iter = children.values().iterator(); iter.hasNext();) {
+ ProposedResourceDelta childDelta = (ProposedResourceDelta) 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)
+ */
+ 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 = (ProposedResourceDelta) current.children.get(path.segment(i));
+ if (current == null)
+ return null;
+ }
+ return current;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceDelta#getAffectedChildren()
+ */
+ public IResourceDelta[] getAffectedChildren() {
+ return getAffectedChildren(ADDED | REMOVED | CHANGED, IResource.NONE);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceDelta#getAffectedChildren(int)
+ */
+ public IResourceDelta[] getAffectedChildren(int kindMask) {
+ return getAffectedChildren(kindMask, IResource.NONE);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceDelta#getAffectedChildren(int, int)
+ */
+ public IResourceDelta[] getAffectedChildren(int kindMask, int memberFlags) {
+ List result = new ArrayList();
+ for (Iterator iter = children.values().iterator(); iter.hasNext();) {
+ ProposedResourceDelta child = (ProposedResourceDelta) iter.next();
+ if ((child.getKind() & kindMask) != 0)
+ result.add(child);
+ }
+ return (IResourceDelta[]) result.toArray(new IResourceDelta[result.size()]);
+ }
+
+ /**
+ * Returns the child delta corresponding to the given child resource name,
+ * or null
.
+ */
+ ProposedResourceDelta getChild(String name) {
+ return (ProposedResourceDelta) children.get(name);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceDelta#getFlags()
+ */
+ public int getFlags() {
+ return status & ~KIND_MASK;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceDelta#getFullPath()
+ */
+ public IPath getFullPath() {
+ return getResource().getFullPath();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceDelta#getKind()
+ */
+ public int getKind() {
+ return status & KIND_MASK;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceDelta#getMarkerDeltas()
+ */
+ public IMarkerDelta[] getMarkerDeltas() {
+ return new IMarkerDelta[0];
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceDelta#getMovedFromPath()
+ */
+ public IPath getMovedFromPath() {
+ return movedFromPath;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceDelta#getMovedToPath()
+ */
+ public IPath getMovedToPath() {
+ return movedToPath;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceDelta#getProjectRelativePath()
+ */
+ public IPath getProjectRelativePath() {
+ return getResource().getProjectRelativePath();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceDelta#getResource()
+ */
+ 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.
+ */
+ public String toString() {
+ return "ProposedDelta(" + resource + ')'; //$NON-NLS-1$
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceAdapterFactory.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 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.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 {
+
+ /* (non-Javadoc)
+ * Method declared on IAdapterFactory
+ */
+ public Object getAdapter(Object adaptableObject, Class adapterType) {
+ if (adapterType == ResourceMapping.class && adaptableObject instanceof IResource) {
+ return new SimpleResourceMapping((IResource) adaptableObject);
+ }
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * Method declared on IAdapterFactory
+ */
+ public Class[] getAdapterList() {
+ return new Class[] {ResourceMapping.class};
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,256 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 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.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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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()
+ */
+ 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)
+ */
+ 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() {
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceModelProvider.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.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.CoreException;
+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 {
+
+ /* (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)
+ */
+ 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)
+ */
+ 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];
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowContainer.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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.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;
+ }
+
+ 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;
+ }
+
+ public int hashCode() {
+ return getResource().hashCode();
+ }
+
+ public Object getAdapter(Class adapter) {
+ if (adapter == IResource.class || adapter == IContainer.class)
+ return container;
+ return super.getAdapter(adapter);
+ }
+
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ShallowResourceMapping.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * 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.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;
+ }
+
+ public Object getModelObject() {
+ return container;
+ }
+
+ public String getModelProviderId() {
+ return ModelProvider.RESOURCE_MODEL_PROVIDER_ID;
+ }
+
+ public IProject[] getProjects() {
+ return new IProject[] { container.getResource().getProject() };
+ }
+
+ public ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) {
+ return new ResourceTraversal[] { new ResourceTraversal(new IResource[] { container.getResource() }, IResource.DEPTH_ONE, IResource.NONE)};
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/SimpleResourceMapping.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.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)
+ */
+ 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.
+ */
+ public Object getModelObject() {
+ return resource;
+ }
+
+ public String getModelProviderId() {
+ return ModelProvider.RESOURCE_MODEL_PROVIDER_ID;
+ }
+
+ /* (non-Javadoc)
+ * Method declared on ResourceMapping.
+ */
+ public IProject[] getProjects() {
+ if (resource.getType() == IResource.ROOT)
+ return ((IWorkspaceRoot)resource).getProjects();
+ return new IProject[] {resource.getProject()};
+ }
+
+ /* (non-Javadoc)
+ * Method declared on ResourceMapping.
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Convert.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Monitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,589 @@
+/*******************************************************************************
+ * 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.File;
+import java.util.*;
+import org.eclipse.core.internal.refresh.RefreshManager;
+import org.eclipse.core.internal.utils.Messages;
+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 {
+ private static final long RESCHEDULE_DELAY = 1000;
+
+ /**
+ * 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;
+ }
+
+ public boolean exists() {
+ return file.exists();
+ }
+
+ 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();
+ }
+
+ 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 (RefreshManager.DEBUG)
+ System.out.println(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 = (ChainedHandle) fileHandleChain.get(i);
+ handle.setPrevious((i > 0) ? (ChainedHandle) fileHandleChain.get(i - 1) : null);
+ handle.setNext((i + 1 < size) ? (ChainedHandle) fileHandleChain.get(i + 1) : this);
+ }
+ setPrevious((size > 0) ? (ChainedHandle) fileHandleChain.get(size - 1) : null);
+ }
+
+ public void destroy() {
+ super.destroy();
+ for (Iterator i = fileHandleChain.iterator(); i.hasNext();) {
+ Handle handle = (Handle) i.next();
+ handle.destroy();
+ }
+ }
+
+ public boolean exists() {
+ IPath location = resource.getLocation();
+ return location == null ? false : location.toFile().exists();
+ }
+
+ public void handleNotification() {
+ if (isOpen()) {
+ postRefreshRequest(resource);
+ findNextChange();
+ }
+ }
+
+ 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;
+ }
+
+ public void handleNotification() {
+ if (isOpen()) {
+ postRefreshRequest(resource);
+ findNextChange();
+ }
+ }
+
+ public void open() {
+ if (!isOpen()) {
+ openHandleOn(resource);
+ }
+ }
+ }
+
+ private static final String DEBUG_PREFIX = "Win32RefreshMonitor: "; //$NON-NLS-1$
+ private static final int WAIT_FOR_MULTIPLE_OBJECTS_TIMEOUT = 300;
+ /**
+ * 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 recieve 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:
This method
+ * splits the list of handles into arrays no larger than
+ * Win32Natives.MAXIMUM_WAIT_OBJECTS. The arrays are balenced 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++] = ((Long) 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 = (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 (RefreshManager.DEBUG)
+ System.out.println(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 = (Handle) i.next();
+ fHandleValueToHandle.remove(new Long(handle.getHandleValue()));
+ handle.destroy();
+ }
+ setHandleValueArrays(createHandleArrays());
+ }
+ }
+
+ /*
+ * @see java.lang.Runnable#run()
+ */
+ protected IStatus run(IProgressMonitor monitor) {
+ long start = -System.currentTimeMillis();
+ if (RefreshManager.DEBUG)
+ System.out.println(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 (RefreshManager.DEBUG)
+ System.out.println(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
+ //make sure it doesn't hog more that 5% of CPU
+ long delay = Math.max(RESCHEDULE_DELAY, start * 30);
+ if (RefreshManager.DEBUG)
+ System.out.println(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()
+ */
+ public boolean shouldRun() {
+ return !fHandleValueToHandle.isEmpty();
+ }
+
+ /*
+ * @see org.eclipse.core.resources.refresh.IRefreshMonitor#unmonitor(IContainer)
+ */
+ 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 = (Handle) fHandleValueToHandle.get(new Long(handleValues[index]));
+ if (handle != null)
+ handle.handleNotification();
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32Natives.java Mon Jun 01 10:16:20 2009 -0500
@@ -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.
+ *
false
true
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/refresh/win32/Win32RefreshProvider.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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
+ *
+ * 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)
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ArrayIterator.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * 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.utils;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * An object that iterates over the elements of an array
+ */
+public class ArrayIterator implements Iterator {
+ Object[] elements;
+ int index;
+ int lastElement;
+
+ /**
+ * Returns new array enumeration over the given object array
+ */
+ public ArrayIterator(Object[] elements) {
+ this(elements, 0, elements.length - 1);
+ }
+
+ /**
+ * Returns new array enumeration over the given object array
+ */
+ public ArrayIterator(Object[] elements, int firstElement, int lastElement) {
+ super();
+ this.elements = elements;
+ index = firstElement;
+ this.lastElement = lastElement;
+ }
+
+ /**
+ * Returns true if this enumeration contains more elements.
+ */
+ public boolean hasNext() {
+ return elements != null && index <= lastElement;
+ }
+
+ /**
+ * Returns the next element of this enumeration.
+ * @exception NoSuchElementException if no more elements exist.
+ */
+ public Object next() throws NoSuchElementException {
+ if (!hasNext())
+ throw new NoSuchElementException();
+ return elements[index++];
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/BitMask.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Cache.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,201 @@
+/*******************************************************************************
+ * 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.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;
+ }
+
+ 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;
+ }
+
+ public Object getKey() {
+ return key;
+ }
+
+ 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;
+ }
+
+ 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();
+ }
+
+ public void dispose() {
+ discardAll();
+ entries = null;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Convert.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * 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
+ * Martin Oberhuber (Wind River) - [44107] Add symbolic links to ResourceAttributes API
+ *******************************************************************************/
+package org.eclipse.core.internal.utils;
+
+import java.io.*;
+import java.net.URI;
+import org.eclipse.core.filesystem.*;
+import org.eclipse.core.internal.resources.ResourceException;
+import org.eclipse.core.internal.resources.Workspace;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.resources.ResourceAttributes;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Static utility methods for manipulating Files and URIs.
+ */
+public class FileUtil {
+ /**
+ * 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());
+ 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;
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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));
+ return attributes;
+ }
+
+ /**
+ * 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.
+ */
+ public static void safeClose(InputStream in) {
+ try {
+ if (in != null)
+ in.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public static void safeClose(OutputStream out) {
+ try {
+ if (out != null)
+ out.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);
+ }
+ if (bytesRead == -1)
+ break;
+ try {
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/IStringPoolParticipant.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/KeyedHashSet.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,242 @@ +/******************************************************************************* + * 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; + + +/** Adapted from a homonym class in org.eclipse.osgi. A hash table of + *KeyedElement
s.
+ */
+
+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;
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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 - Initial API and implementation
+ *******************************************************************************/
+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;
+
+ // 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_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_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_emptyLinkName;
+ public static String projRead_failureReadingProjectDesc;
+ public static String projRead_notProjectDescription;
+ public static String projRead_whichKey;
+ public static String projRead_whichValue;
+
+ 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_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_projectPath;
+ 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_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 synchronizer_partnerNotRegistered;
+
+ // URL
+ public static String url_badVariant;
+ public static String url_couldNotResolve;
+
+ // 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/ObjectMap.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,314 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.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.
+ */
+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()
+ */
+ public void clear() {
+ elements = null;
+ count = 0;
+ }
+
+ /**
+ * @see java.lang.Object#clone()
+ */
+ public Object clone() {
+ return new ObjectMap(this);
+ }
+
+ /**
+ * @see Map#containsKey(java.lang.Object)
+ */
+ 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)
+ */
+ 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.
+ */
+ public Set entrySet() {
+ return count == 0 ? Collections.EMPTY_SET : toHashMap().entrySet();
+ }
+
+ /**
+ * See Object#equals
+ */
+ 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)
+ */
+ 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
+ */
+ 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()
+ */
+ 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.
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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()
+ */
+ public int size() {
+ return count;
+ }
+
+ /* (non-Javadoc
+ * Method declared on IStringPoolParticipant
+ */
+ 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(elements[i], 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.
+ */
+ 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;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Policy.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.internal.utils;
+
+import java.util.Date;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.*;
+import org.osgi.framework.Bundle;
+
+public class Policy {
+ 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_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;
+
+ static {
+ //init debug options
+ if (ResourcesPlugin.getPlugin().isDebugging()) {
+ DEBUG = true;
+ String sTrue = Boolean.TRUE.toString();
+ DEBUG_AUTO_REFRESH = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/refresh")); //$NON-NLS-1$
+
+ DEBUG_BUILD_DELTA = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/build/delta")); //$NON-NLS-1$
+ DEBUG_BUILD_FAILURE = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/build/failure")); //$NON-NLS-1$
+ DEBUG_BUILD_INVOKING = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/build/invoking")); //$NON-NLS-1$
+ DEBUG_BUILD_INTERRUPT = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/build/interrupt")); //$NON-NLS-1$
+ DEBUG_BUILD_NEEDED = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/build/needbuild")); //$NON-NLS-1$
+ DEBUG_BUILD_NEEDED_STACK = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/build/needbuildstack")); //$NON-NLS-1$
+ DEBUG_BUILD_STACK = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/build/stacktrace")); //$NON-NLS-1$
+
+ DEBUG_CONTENT_TYPE = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/contenttype")); //$NON-NLS-1$
+ DEBUG_CONTENT_TYPE_CACHE = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/contenttype/cache")); //$NON-NLS-1$
+ DEBUG_HISTORY = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/history")); //$NON-NLS-1$
+ DEBUG_NATURES = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/natures")); //$NON-NLS-1$
+ DEBUG_PREFERENCES = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/preferences")); //$NON-NLS-1$
+
+ DEBUG_RESTORE = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/restore")); //$NON-NLS-1$
+ DEBUG_RESTORE_MARKERS = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/restore/markers")); //$NON-NLS-1$
+ DEBUG_RESTORE_MASTERTABLE = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/restore/mastertable")); //$NON-NLS-1$
+ DEBUG_RESTORE_METAINFO = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/restore/metainfo")); //$NON-NLS-1$
+ DEBUG_RESTORE_SNAPSHOTS = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/restore/snapshots")); //$NON-NLS-1$
+ DEBUG_RESTORE_SYNCINFO = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/restore/syncinfo")); //$NON-NLS-1$
+ DEBUG_RESTORE_TREE = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/restore/tree")); //$NON-NLS-1$
+
+ DEBUG_SAVE = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/save")); //$NON-NLS-1$
+ DEBUG_SAVE_MARKERS = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/save/markers")); //$NON-NLS-1$
+ DEBUG_SAVE_MASTERTABLE = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/save/mastertable")); //$NON-NLS-1$
+ DEBUG_SAVE_METAINFO = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/save/metainfo")); //$NON-NLS-1$
+ DEBUG_SAVE_SYNCINFO = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/save/syncinfo")); //$NON-NLS-1$
+ DEBUG_SAVE_TREE = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/save/tree")); //$NON-NLS-1$
+
+ DEBUG_STRINGS = sTrue.equalsIgnoreCase(Platform.getDebugOption(ResourcesPlugin.PI_RESOURCES + "/strings")); //$NON-NLS-1$
+ }
+ }
+
+ public static void checkCanceled(IProgressMonitor monitor) {
+ if (monitor.isCanceled())
+ throw new OperationCanceledException();
+ }
+
+ /**
+ * 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 debug(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());
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Queue.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,197 @@
+/*******************************************************************************
+ * 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.util.Collections;
+import java.util.Iterator;
+
+/**
+ * A Queue of objects.
+ */
+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(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 Object elementAt(int index) {
+ return elements[index];
+ }
+
+ 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 Object getNextAvailableObject() {
+ int index = tail;
+ while (index != head) {
+ if (elements[index] != null) {
+ Object result = 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(Object 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 Object peek() {
+ return elements[head];
+ }
+
+ public Object peekTail() {
+ return elements[decrement(tail)];
+ }
+
+ public Object remove() {
+ if (isEmpty())
+ return null;
+ Object result = peek();
+ if (!reuse)
+ elements[head] = null;
+ head = increment(head);
+ return result;
+ }
+
+ public Object removeTail() {
+ Object 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);
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPool.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * 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;
+
+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 aString
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/StringPoolJob.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 - Initial API and implementation
+ *******************************************************************************/
+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
+ */
+ 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 = (Map.Entry[]) participants.entrySet().toArray(new Map.Entry[0]);
+ ISchedulingRule[] rules = new ISchedulingRule[entries.length];
+ IStringPoolParticipant[] toRun = new IStringPoolParticipant[entries.length];
+ for (int i = 0; i < toRun.length; i++) {
+ toRun[i] = (IStringPoolParticipant) entries[i].getKey();
+ rules[i] = (ISchedulingRule) 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() {
+ public void handleException(Throwable exception) {
+ //exceptions are already logged, so nothing to do
+ }
+
+ public void run() {
+ current.shareStrings(pool);
+ }
+ });
+ }
+ return pool.getSavedStringCount();
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/UniversalUniqueIdentifier.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,349 @@
+/*******************************************************************************
+ * 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.*;
+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.
+ */
+ 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;
+ }
+
+ 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;
+ }
+ }
+
+ private static byte[] getNodeAddress() {
+ return nodeAddress;
+ }
+
+ 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;
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/WrappedRuntimeException.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * 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.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;
+ }
+
+ public String getMessage() {
+ return target.getMessage();
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/DefaultElementComparator.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * 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.
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTree.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,728 @@
+/*******************************************************************************
+ * 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.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 = (ElementTree) 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() {
+ 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();
+ }
+
+ public String toString() {
+ return "ElementTree(" + treeStamp + ")"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeIterator.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * 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.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:
+
+ */
+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();
+ for (int i = children.length; --i >= 0;) {
+ 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();
+ for (int i = children.length; --i >= 0;) {
+ 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);
+ }
+ }
+
+ public String requestName() {
+ if (nextFreeSegment == 0)
+ return ""; //$NON-NLS-1$
+ return segments[nextFreeSegment - 1];
+ }
+
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReader.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * 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.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.*;
+
+/**
+ // printing a crude representation of the poster child
+ IElementContentVisitor visitor=
+ new IElementContentVisitor() {
+ public void visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
+ System.out.println(requestor.requestPath() + " -> " + elementContent);
+ }
+ });
+ ElementTreeIterator iterator = new ElementTreeIterator(tree, Path.ROOT);
+ iterator.iterate(visitor);
+
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() {
+ public void writeData(IPath path, Object data, DataOutput output) {
+ //not needed
+ }
+
+ 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 {
+ /* Dispatch to the appropriate reader. */
+ ElementTreeReader realReader = getReader(readNumber(input));
+ return realReader.readDeltaChain(input);
+ }
+
+ /**
+ * 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 {
+ /* Dispatch to the appropriate reader. */
+ ElementTreeReader realReader = getReader(readNumber(input));
+ return realReader.readTree(input);
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeReaderImpl_1.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * 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.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.
+ */
+ public ElementTree readDelta(ElementTree parentTree, DataInput input) throws IOException {
+ DeltaDataTree complete = parentTree.getDataTree();
+ DeltaDataTree delta = dataTreeReader.readTree(complete, input);
+
+ //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;
+ }
+
+ /**
+ * 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 {
+ /* 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);
+
+ /* 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;
+ }
+
+ /** Part of ElementTreeReader
interface.
+ * @see ElementTreeReader
+ */
+ public ElementTree readTree(DataInput input) throws IOException {
+
+ /* The format version number has already been consumed
+ * by ElementTreeReader#readFrom.
+ */
+ ElementTree result = new ElementTree(dataTreeReader.readTree(null, input));
+ return result;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,237 @@
+/*******************************************************************************
+ * 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 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() { + 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); + } + } + + 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 = (List) 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 = (List) table.remove(oldest); + for (Enumeration e = Collections.enumeration(indices); e.hasMoreElements();) { + Integer next = (Integer) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementComparator.java Mon Jun 01 10:16:20 2009 -0500 @@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementContentVisitor.java Mon Jun 01 10:16:20 2009 -0500 @@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementTreeData.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IPathRequestor.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ICommand.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * 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;
+
+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. + */ +public interface ICommand { + + /** + * Returns a table of the arguments for this command, ornull
+ * 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 + * onIncrementalProjectBuilder
+ * @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 typeString
), 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.
+ *
+ * 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. + *
+ * + * @param kind One of the *_BUILD constants defined + * onIncrementalProjectBuilder
+ * @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)
+ * @since 3.1
+ */
+ public void setBuilding(int kind, boolean value);
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IContainer.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,467 @@
+/*******************************************************************************
+ * 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;
+
+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.
+ *
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 (project, folder, or file)
+ * with the given name in this container, or null
if no such
+ * resource exists.
+ *
+ * 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 name the string name of the member resource + * @return the member resource, ornull
if no such
+ * resource exists
+ */
+ public IResource findMember(String name);
+
+ /**
+ * Finds and returns the member resource (project, folder, or file)
+ * with the given name in this container, or null
if
+ * there is no such resource.
+ *
+ * If the includePhantoms
argument is false
,
+ * only a member resource with the given name 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 name.
+ *
+ * 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 + * existing resource (or phantom) in the workspace. + *
+ * + * @param name the string name of the member resource + * @param includePhantomstrue
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()
+ * @see IResource#isPhantom()
+ */
+ public IResource findMember(String name, 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, ornull
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 there is no such resource.
+ * 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 resource that exists at the given path will be returned.
+ * If the includePhantoms
argument is true
,
+ * the method also returns a resource if the workspace is keeping track of
+ * a phantom member resource at the given 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 + * existing resource (or phantom) at the calculated path in the workspace. + *
+ * + * @param path the path of the desired resource + * @param includePhantomstrue
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.
+ *
+ * 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:
+ *
+ * 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.
+ *
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 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:
+ * includePhantoms
is false
and
+ * this resource does not exist.includePhantoms
is false
and
+ * this resource is a project that is not open.
+ * 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.
+ *
INCLUDE_PHANTOMS
flag is not specified and
+ * this resource does not exist.INCLUDE_PHANTOMS
flag is not specified and
+ * this resource is a project that is not open.+ * 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 ofDEPTH_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:
+ * 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, ornull
+ * @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:
+ * 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. Returnsnull
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.
+ *
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,1095 @@
+/*******************************************************************************
+ * 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;
+
+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.
+ *
+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.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:
+ * FORCE
is not specified.IResourceChangeEvent
for more details.+ * 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, + * ornull
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:
+ * IWorkspace.validateName
).force
is false
.IResourceChangeEvent
for more details.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.
+ *
+ * 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, + * ornull
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:
+ * IWorkspace.validateName
).FORCE
is not specified.IResourceChangeEvent
for more details.+ * 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 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. + *
+ *+ * Update flags other than {@link IResource#ALLOW_MISSING_LOCAL} or + * {@link IResource#REPLACE} 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} and {@link IResource#REPLACE}) + * @param monitor a progress monitor, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IWorkspace.validateName
).ALLOW_MISSING_LOCAL
is
+ * not specified.IResourceChangeEvent
for more details.
+ * 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. + *
+ *+ * Update flags other than {@link IResource#ALLOW_MISSING_LOCAL} or + * {@link IResource#REPLACE} 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} and {@link IResource#REPLACE}) + * @param monitor a progress monitor, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IWorkspace.validateName
).ALLOW_MISSING_LOCAL
is
+ * not specified.IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.+ * 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: + *
+ * 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:
+ *
IContainer#getDefaultCharset
).
+ * 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, ornull
+ * @exception CoreException if this method fails. Reasons include:
+ * + * This method uses the following algorithm to determine the charset to be returned: + *
IContainer#getDefaultCharset
).
+ * 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.
+ *
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
+ * IFile
s, clients should call this method instead of
+ * IContentTypeManager.getDescriptionFor
.
+ *
null
+ * @exception CoreException if this method fails. Reasons include:
+ * IStorage
method
+ * returns an open input stream on the contents of this file.
+ * 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:
+ * 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:
+ * false
.IStorage
and IResource
+ * methods links the semantics of resource and storage object paths such that
+ * IFile
s always have a path and that path is relative to the
+ * containing workspace.
+ *
+ * @see IResource#getFullPath()
+ * @see IStorage#getFullPath()
+ */
+ 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, ornull
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
+ * IFile
s always have a name and that name equivalent to the
+ * last segment of its full path.
+ *
+ * @see IResource#getName()
+ * @see IStorage#getName()
+ */
+ 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()
+ */
+ 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this resource could not be moved. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.null
+ * will remove the charset setting for this resource.
+ *
+ * @param newCharset a charset name, or null
+ * @exception CoreException if this method fails. Reasons include:
+ * 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, ornull
+ * @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 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.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:
+ * FORCE
is not specified.IResourceChangeEvent
for more details.
+ * 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:
+ * FORCE
is not specified.IResourceChangeEvent
for more details.+ * 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 + */ +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 isnull
, 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IFileState.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * 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;
+
+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.
+ *
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:
+ * IStorage
+ * method specifies that IFileState
s 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()
+ */
+ 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 IFileState
s 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()
+ */
+ public String getName();
+
+ /**
+ * Returns whether this file state is read-only.
+ * This refinement of the corresponding
+ * IStorage
method restricts IFileState
s to
+ * always be read-only.
+ *
+ * @see IStorage
+ */
+ public boolean isReadOnly();
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IFolder.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,434 @@
+/*******************************************************************************
+ * 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;
+
+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.
+ *
+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IWorkspace.validateName
).force
is false
.IResourceChangeEvent
for more details.
+ * 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.
+ *
+ * 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}, and {@link IResource#TEAM_PRIVATE}) + * @param local a flag controlling whether or not the folder will be local + * after the creation + * @param monitor a progress monitor, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IWorkspace.validateName
).FORCE
is not specified.IResourceChangeEvent
for more details.
+ * 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. + *
+ *+ * Update flags other than {@link IResource#ALLOW_MISSING_LOCAL} or + * {@link IResource#REPLACE} or {@link IResource#BACKGROUND_REFRESH} 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 + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param monitor a progress monitor, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IWorkspace.validateName
).ALLOW_MISSING_LOCAL
is
+ * not specified.IResourceChangeEvent
for more details.
+ * 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. + *
+ *+ * Update flags other than {@link IResource#ALLOW_MISSING_LOCAL} or + * {@link IResource#REPLACE} or {@link IResource#BACKGROUND_REFRESH} 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 + * (only ALLOW_MISSING_LOCAL is relevant here) + * @param monitor a progress monitor, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IWorkspace.validateName
).ALLOW_MISSING_LOCAL
is
+ * not specified.IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this resource could not be moved. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.
+ * 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.
+ *
+ * Each marker has:
"org.eclipse.core.resources.taskmarker"
), + * 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
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:
+ *
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.
+ *
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:
+ * IResourceChangeEvent
for more details.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:
+ * 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:
+ * 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:
+ * 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:
+ * null
.
+ *
+ * @return the type of this marker
+ * @exception CoreException if this method fails. Reasons include:
+ * true
if 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 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: + *IResourceChangeEvent
for more details.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.
+ * + * 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, ornull
if the attribute is to be undefined
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.+ * 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: + *IResourceChangeEvent
for more details.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.
+ * + * 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: + *IResourceChangeEvent
for more details.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.
+ * + * 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:
+ * IResourceChangeEvent
for more details.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.
+ *
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.
+ *
+ * If kind is IResourceDelta.ADDED
, then the information is
+ * from the new marker, otherwise it is from the old marker.
+ *
+ * If kind is IResourceDelta.ADDED
, then the information is
+ * from the new marker, otherwise it is from the old marker.
+ *
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.
+ *
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.
+ *
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.
+ *
+ * If kind is IResourceDelta.ADDED
, then the information is
+ * from the new marker, otherwise it is from the old marker.
+ *
true
if the marker's type
+ * is the same as (or a sub-type of) the given type.
+ */
+ public boolean isSubtypeOf(String superType);
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeEvent.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * 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;
+
+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.
+ */
+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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IPathVariableChangeListener.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.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.
+ *
+ * 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. + */ +public interface IPathVariableManager { + + /** + * 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: + *+ *
null
.
+ * null
.null
.
+ * 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 benull
)
+ * @exception CoreException if this method fails. Reasons include:
+ * 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
+ */
+ public IPath getValue(String name);
+
+ /**
+ * Returns an array containing all defined path variable names.
+ *
+ * @return an array containing all defined path variable names
+ */
+ public String[] getPathVariableNames();
+
+ /**
+ * 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
.
+ *
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: + *
+ *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 ornull
+ */
+ 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);
+
+ /**
+ * 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);
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IProject.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,845 @@
+/*******************************************************************************
+ * 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;
+
+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: + *
+ * Projects implement the IAdaptable
interface;
+ * extensions are managed by the platform's adapter manager.
+ *
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 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: + *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. + *
+ *+ * 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.
+ * 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;
+
+ /**
+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IWorkspace.validateName
).IWorkspace.validateProjectLocation
).IResourceChangeEvent
for more details.+ * 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: + *
+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IWorkspace.validateName
).IWorkspace.validateProjectLocation
).IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IWorkspace.validateName
).IWorkspace.validateProjectLocation
).IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.+ * 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 ornull
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:
+ * 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 UseIProject.getWorkingLocation(plugin.getUniqueIdentifier())
.
+ */
+ 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: + *true
if the project has the given nature
+ * @exception CoreException if this method fails. Reasons include:
+ * + *
true
if the given nature is enabled for this project
+ * @exception CoreException if this method fails. Reasons include:
+ * + * 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. + *
+ * + * @returntrue
if this project is open, false
if
+ * this project is closed or does not exist
+ * @see #open(IProgressMonitor)
+ * @see #close(IProgressMonitor)
+ */
+ public boolean isOpen();
+
+ /**
+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this resource could not be moved. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.+ * Opening a project constructs an in-memory representation + * of its resources from information stored on disk. + *
+ *
+ * The BACKGROUND_REFRESH
update flag controls how
+ * this method behaves when a project is opened for the first time on a location
+ * that has existing resources on disk. If this 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. If this flag is not specified, resources on disk are added
+ * to the project in the foreground before this method returns.
+ *
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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.
+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.+ * 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:
+ * FORCE
is not
+ * specified.IResourceChangeEvent
for more details.".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 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 #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.
+ */
+ 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()
+ * @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.
+ *
+ * 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 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 ornull
+ * @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 ornull
+ * @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 #getReferencedProjects() + */ + public void setReferencedProjects(IProject[] projects); +} diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNature.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,81 @@ +/******************************************************************************* + * 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.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 usingIProject.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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IProjectNatureDescriptor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * 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.
+ *
+ * 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"
.
+ *
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResource.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,2545 @@
+/*******************************************************************************
+ * 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
+ * Red Hat Incorporated - get/setResourceAttribute code
+ * Oakland Software Incorporated - added getSessionProperties and getPersistentProperties
+ *******************************************************************************/
+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 implement the IAdaptable
interface;
+ * extensions are managed by the platform's adapter manager.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Example usage:
+ *
+ *
+ *
+ * delete(IResource.NONE, null)
+ *
+ *
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.
+ *
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. + *
+ * + * @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 + * @exception CoreException if this request fails. Reasons include: + *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)
.
+ *
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.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. + *
+ * + * @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 + * @exception CoreException if this request fails. Reasons include: + *+ * This operation is long-running; progress and cancellation are provided + * by the given progress monitor. + *
+ * @param monitor a progress monitor, ornull
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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this resource could not be copied. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this resource could not be copied. Reasons include:
+ * SHALLOW
is specified.FORCE
is not specified.IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this resource could not be copied. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.+ * 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:
+ * FORCE
is not specified.IResourceChangeEvent
for more details.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:
+ * + * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.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, ornull
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:
+ * IResourceChangeEvent
for more details.
+ * 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. + *
+ * + * @returntrue
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:
+ * 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:
+ * includeSubtypes
is 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:
+ * 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.
+ *
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
.
+ *
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: + *
NULL_STAMP
)touch
ing a resourceNULL_STAMP
)NULL_STAMP
,
+ destination changes from NULL_STAMP
)NULL_STAMP
)NULL_STAMP
)NULL_STAMP
)NULL_STAMP
)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 resource which is the parent of this resource, + * ornull
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, + * ornull
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:
+ * 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:
+ * 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, ornull
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 file system 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.
+ * + * 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 #getLocationURI()}. + *
+ * + * @return the raw path of this resource in the file system, or + *null
if no path 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:
+ *
+ * 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);
+ * }
+ *
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:
+ * 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:
+ * FILE
,
+ * FOLDER
, PROJECT
, ROOT
.
+ * + *
FILE
implement IFile
.FOLDER
implement IFolder
.PROJECT
implement IProject
.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. + * + * @returntrue
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)
.
+ *
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.
+ *
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()}. + *
+ * + * @returntrue
if this resource is hidden , and
+ * false
otherwise
+ * @see #setHidden(boolean)
+ * @since 3.4
+ */
+ public boolean isHidden();
+
+ /**
+ * 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)
.
+ *
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 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 only 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.
+ *
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 areDEPTH_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.
+ */
+ 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)
).
+ *
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 IResource#getResourceAttributes()
+ */
+ 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: + *
+ * 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 ofIResource.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();
+
+ /**
+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this resource could not be moved. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.+ * 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_HISTORY
and
+ * 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:
+ * SHALLOW
is specified.force
is false
.IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this resource could not be moved. Reasons include:
+ * force
is false
.IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this resource could not be moved. Reasons include:
+ * FORCE
is not specified.IResourceChangeEvent
for more details.+ * 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 areDEPTH_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:
+ * IResourceChangeEvent
for more details.
+ * 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:
+ *
IResourceChangeEvent
for more details.+ * 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 isDerivedtrue
if this resource is to be marked
+ * as derived, and false
otherwise
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.+ * 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 isHiddentrue
if this resource is to be marked
+ * as hidden, and false
otherwise
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.false
)
+ * has no affect 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 areDEPTH_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:
+ * IResourceChangeEvent
for more details.IResourceChangeEvent
for more details.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"
).
+ *
null
if the property is to be removed
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.true
to set it to read-only,
+ * false
to unset
+ * @deprecated use IResource#setResourceAttributes(ResourceAttributes)
+ */
+ 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: + *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"
).
+ *
null
if the property is to be removed
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.+ * 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 isTeamPrivatetrue
if this resource is to be marked
+ * as team private, and false
otherwise
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.+ * There are currently five different types of resource change events: + *
PRE_BUILD
, and getDelta
returns
+ * the hierarchical delta. The resource delta is 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.
+ * POST_BUILD
, and getDelta
returns
+ * the hierarchical delta. The resource delta is 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.
+ * 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.
+ * PRE_CLOSE
,
+ * and getResource
returns the project being closed.
+ * The workspace is closed for change during notification of these events.
+ * PRE_DELETE
,
+ * and getResource
returns the project being deleted.
+ * The workspace is closed for change during notification of these events.
+ * PRE_REFRESH
,
+ * and getResource
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. + */ +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 bygetDelta
.
+ * 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
+ */
+ public static final int PRE_AUTO_BUILD = 8;
+
+ /**
+ * 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 #getResource()
+ * @since 3.0
+ */
+ public static final int PRE_BUILD = 8;
+
+ /**
+ * @deprecated This event type has been renamed to
+ * POST_BUILD
+ */
+ public static final int POST_AUTO_BUILD = 16;
+
+ /**
+ * 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 #getResource()
+ * @since 3.0
+ */
+ public static final int POST_BUILD = 16;
+
+ /**
+ * Event type constant (bit mask) indicating a before-the-fact
+ * report of refreshing of a project.
+ * See class comments for further details.
+ *
+ * @see #getType()
+ * @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, ornull
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.
+ *
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 a PRE_CLOSE
or
+ * PRE_DELETE
event then the resource
+ * will be the affected project. Otherwise the resource will
+ * be null
.
+ *
null
if not applicable
+ */
+ public IResource getResource();
+
+ /**
+ * Returns an object identifying the source of this event.
+ *
+ * If the event is a PRE_BUILD
or POST_BUILD
+ * then this will be the scope of the build (either the {@link IWorkspace} or a single {@link IProject}).
+ *
+ * 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,571 @@ +/******************************************************************************* + * 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; + +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.
+ *
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;
+
+ /**
+ * 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.
+ *
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.
+ *
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, ornull
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.
+ *
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.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.REMOVED
+ * (or CHANGED
in conjunction with REPLACED
):
+ * MOVED_TO
- The resource has moved.
+ * getMovedToPath
will return the path of where it was moved to.ADDED
+ * (or CHANGED
in conjunction with REPLACED
):
+ * MOVED_FROM
- The resource has moved.
+ * getMovedFromPath
will return the path of where it was moved from.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#DESCRIPTION + * @see IResourceDelta#ENCODING + * @see IResourceDelta#OPEN + * @see IResourceDelta#MOVED_TO + * @see IResourceDelta#MOVED_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 ofADDED
,
+ * 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDeltaVisitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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.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. + * + * @returntrue
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxy.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * 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;
+
+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. + */ +public interface IResourceProxy { + /** + * Returns the modification stamp of the resource being visited. + * + * @return the modification stamp, orNULL_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, + * ornull
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceProxyVisitor.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,52 @@ +/******************************************************************************* + * 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.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 + * @returntrue
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceRuleFactory.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.resources;
+
+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 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.
+ */
+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 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 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 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceStatus.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,314 @@
+/*******************************************************************************
+ * 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;
+
+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.
+ */
+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;
+
+ // 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceVisitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.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 + * @returntrue
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ISaveContext.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,196 @@
+/*******************************************************************************
+ * 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;
+
+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.
+ *
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
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.
+ *
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.
+ *
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.
+ *
+ * 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. + *
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ISavedState.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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; + +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. + */ +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: + *
+ * 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IStorage.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,68 @@ +/******************************************************************************* + * 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.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 anIFile
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ISynchronizer.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * 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;
+
+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.
+ */
+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:
+ * IResourceStatus.PARTNER_NOT_REGISTERED
+ The sync partner is not registered.IResource.DEPTH_ZERO
, IResource.DEPTH_ONE
,
+ * or IResource.DEPTH_INFINITE
.
+ * @exception CoreException if this operation fails. Reasons include:
+ * IResourceStatus.PARTNER_NOT_REGISTERED
+ The sync partner is not registered.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.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, ornull
+ * @exception CoreException if this operation fails. Reasons include:
+ * IResourceStatus.PARTNER_NOT_REGISTERED
+ The sync partner is not registered.+ * 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 snapshooting 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.
+ *
+ * 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.
+ *
+ * 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:
+ * + * 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 + *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 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;
+
+ /**
+ * 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 project 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 byIWorkspace.computeProjectOrder
,
+ * which produces a more usable result when there are cycles in project
+ * reference graph.
+ */
+ 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 ofprojects
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
+ * 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 project 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, ornull
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:
+ *
+ * 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, ornull
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:
+ * FORCE
is not specified.IResourceChangeEvent
for
+ * more details.+ * 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, ornull
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: + *
+ * 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, ornull
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: + *IResourceChangeEvent
for
+ * more details.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.
+ *
null
+ * @see ISaveContext#needDelta()
+ */
+ public void forgetSavedTree(String pluginId);
+
+ /**
+ * 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 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: + *+ * 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, ornull
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:
+ *
force
flag has the same meaning as it does on the
+ * corresponding single-resource method.+ * 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, ornull
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:
+ * FORCE
is false
.
+ * IResourceChangeEvent
for
+ * more details.+ * The project description is initialized to: + *
+ * 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) + */ + public void removeSaveParticipant(Plugin plugin); + + /** + * 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, then any attempt to change resources will fail. 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.
+ *
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: + *
save
save
save
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. + *
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).prepareToSave(context)
, passing in its own context
+ * object.
+ * prepareToSave
fails (throws an exception), the problem
+ * is logged and the participant is marked as unstable.saving(context)
, passing in its
+ * own context object.
+ * 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.saving
fails (throws an exception), the problem is
+ * logged and the participant is marked as unstable.doneSaving(context)
, passing in
+ * its own context object.
+ * 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.)
+ * rollback(context)
, passing in
+ * its own context object.
+ * 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.)+ * 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 fulltrue
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:
+ * + * This method is for internal use by the platform-related plug-ins. Clients + * should not call this method. + *
+ * + * @param lock the lock to install on this workspace. + * + * @deprecated it is no longer possible to override the workspace lock + * behavior. This functionality is now provided in the platform API by + * subclassing the {@link LockListener} class. + */ + public void setWorkspaceLock(WorkspaceLock lock); + + /** + * 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.
+ *
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 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:
+ * + * This method also checks that the given resource can legally become a + * linked resource. This includes the following restrictions: + *
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:
+ *
+ * This method also checks that the given resource can legally become a + * linked resource. This includes the following restrictions: + *
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: + *
+ * An empty nature set is always valid. + *
+ * + * @param natureIds an array of nature extension identifiers + * @return a status object with codeIStatus.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:
+ *
validateName
+ * + * 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:
+ * + * 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 benull
+ * 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:
+ * + * 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 benull
+ * 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,217 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * 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.
+ */
+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.
+ *
+ * @return the maximum time a file state should be kept in the local history
+ * represented in milliseconds
+ * @see #setFileStateLongevity(long)
+ * @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.
+ *
+ * @return the maximum number of states per file that can be stored in the local history
+ * @see #setMaxFileStates(int)
+ * @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.
+ *
+ * @return the maximum permitted size of a file to be stored in the local history
+ * @see #setMaxFileStateSize(long)
+ * @see ResourcesPlugin#PREF_MAX_FILE_STATE_SIZE
+ */
+ public long getMaxFileStateSize();
+
+ /**
+ * 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.
+ *
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.
+ *
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.
+ *
+ * Users must call IWorkspace.setDescription
before changes
+ * made to this description take effect.
+ *
+ * Users must call IWorkspace.setDescription
before changes
+ * made to this description take effect.
+ *
+ * Users must call IWorkspace.setDescription
before changes
+ * made to this description take effect.
+ *
+ * Users must call IWorkspace.setDescription
before changes
+ * made to this description take effect.
+ *
+ * Users must call IWorkspace.setDescription
before changes
+ * made to this description take effect.
+ *
+ * Workspace roots implement the IAdaptable
interface;
+ * extensions are managed by the platform's adapter manager.
+ *
+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ * IResourceChangeEvent
for more details.
+ * 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 not contain {@link IResource#HIDDEN} projects along with + * their children. + *
+ * @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 + */ + 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 not contain {@link IResource#HIDDEN} projects along with + * their children. + *
+ * @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 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 not contain files contained in {@link IResource#HIDDEN} projects. + *
+ * @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 + */ + 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 not contain files contained in {@link IResource#HIDDEN} projects. + *
+ * @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 a handle to the workspace root, project or folder + * which is mapped to the given path + * in the local file system, ornull
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. + *
+ *
+ * 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
.
+ *
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. + *
+ *
+ * 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
.
+ *
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.
+ *
+ * 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceRunnable.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,42 @@ +/******************************************************************************* + * 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.resources; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A runnable which executes as a batch operation within the workspace. + * TheIWorkspaceRunnable
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
.
+ *
null
if progress
+ * reporting and cancellation are not desired
+ * @exception CoreException if this operation fails.
+ */
+ public void run(IProgressMonitor monitor) throws CoreException;
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,345 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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;
+
+import java.util.Map;
+import org.eclipse.core.internal.events.InternalBuilder;
+import org.eclipse.core.runtime.*;
+
+/**
+ * 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: + *
build
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.
+ *
+ * @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.
+ *
+ * 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.
+ *
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)
+ */
+ 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
.
+ *
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
+ */
+ protected void clean(IProgressMonitor monitor) throws CoreException {
+ //default implementation does nothing
+ if (false)
+ throw new CoreException(Status.OK_STATUS);//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.
+ */
+ public final void forgetLastBuiltState() {
+ super.forgetLastBuiltState();
+ }
+
+ /**
+ * 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 + */ + 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. + *
+ * + * @return the resource delta for the project ornull
+ */
+ public final IResourceDelta getDelta(IProject project) {
+ return super.getDelta(project);
+ }
+
+ /**
+ * Returns the project for which this builder is defined.
+ *
+ * @return the project
+ */
+ public final IProject getProject() {
+ return super.getProject();
+ }
+
+ /**
+ * 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
.
+ *
true
if the given project has been built in this
+ * iteration, and false
otherwise.
+ * @see #needRebuild()
+ * @since 2.1
+ */
+ 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
+ */
+ public final boolean isInterrupted() {
+ return super.isInterrupted();
+ }
+
+ /**
+ * Indicates that this builder made changes that affect a project that
+ * precedes this project 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 + */ + 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")); + * } + * } + *+ * + * + */ + public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException { + //thwart compiler warning + if (false) + throw new CoreException(Status.OK_STATUS); + } + + /** + * 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)
+ */
+ protected void startupOnInitialize() {
+ // reserved for future use
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ProjectScope.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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;
+
+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.IScopeContext;
+
+/**
+ * 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 implements IScopeContext { + + /** + * 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)
+ */
+ 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()
+ */
+ 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()
+ */
+ public String getName() {
+ return SCOPE;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ 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()
+ */
+ public int hashCode() {
+ return super.hashCode() + context.getFullPath().hashCode();
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourceAttributes.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,191 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 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.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.
+ *
+ * @return true
if this resource is marked archive,
+ * false
otherwise
+ * @see #setArchive(boolean)
+ */
+ public boolean isArchive() {
+ return (attributes & EFS.ATTRIBUTE_ARCHIVE) != 0;
+ }
+
+ /**
+ * Returns whether this ResourceAttributes object is marked executable.
+ *
+ * @return true
if this resource is marked executable,
+ * false
otherwise
+ * @see #setExecutable(boolean)
+ */
+ public boolean isExecutable() {
+ return (attributes & EFS.ATTRIBUTE_EXECUTABLE) != 0;
+ }
+
+ /**
+ * Returns whether this ResourceAttributes object is marked hidden.
+ *
+ * @return true
if this resource is marked hidden,
+ * false
otherwise
+ * @see #setHidden(boolean)
+ * @since 3.2
+ */
+ public boolean isHidden() {
+ return (attributes & EFS.ATTRIBUTE_HIDDEN) != 0;
+ }
+
+ /**
+ * Returns whether this ResourceAttributes object is marked read only.
+ *
+ * @return true
if this resource is marked as read only,
+ * false
otherwise
+ * @see #setReadOnly(boolean)
+ */
+ public boolean isReadOnly() {
+ return (attributes & EFS.ATTRIBUTE_READ_ONLY) != 0;
+ }
+
+ /**
+ * Returns whether this ResourceAttributes object is marked read only.
+ *
+ * @return true
if this resource is marked as symbolic link,
+ * false
otherwise
+ * @see #setSymbolicLink(boolean)
+ * @since 3.4
+ */
+ public boolean isSymbolicLink() {
+ return (attributes & EFS.ATTRIBUTE_SYMLINK) != 0;
+ }
+
+ /**
+ * Sets or unsets whether this ResourceAttributes object is marked archive.
+ *
+ * @param archive true
to set it to be archive,
+ * false
to unset
+ * @see #isArchive()
+ */
+ public void setArchive(boolean archive) {
+ set(EFS.ATTRIBUTE_ARCHIVE, archive);
+ }
+
+ /**
+ * Clears all of the bits indicated by the mask.
+ */
+ private void set(int mask, boolean value) {
+ if (value)
+ attributes |= mask;
+ else
+ attributes &= ~mask;
+ }
+
+ /**
+ * Sets or unsets whether this ResourceAttributes object is marked executable.
+ *
+ * @param executable true
to set it to be executable,
+ * false
to unset
+ * @see #isExecutable()
+ */
+ public void setExecutable(boolean executable) {
+ set(EFS.ATTRIBUTE_EXECUTABLE, executable);
+ }
+
+ /**
+ * Sets or unsets whether this ResourceAttributes object is marked hidden
+ *
+ * @param hidden true
to set it to be marked hidden,
+ * false
to unset
+ * @see #isHidden()
+ * @since 3.2
+ */
+ public void setHidden(boolean hidden) {
+ set(EFS.ATTRIBUTE_HIDDEN, hidden);
+ }
+
+ /**
+ * Sets or unsets whether this ResourceAttributes object is marked read only.
+ *
+ * @param readOnly true
to set it to be marked read only,
+ * false
to unset
+ * @see #isReadOnly()
+ */
+ public void setReadOnly(boolean readOnly) {
+ set(EFS.ATTRIBUTE_READ_ONLY, readOnly);
+ }
+
+ /**
+ * Sets or unsets whether this ResourceAttributes object is marked as symbolic link.
+ *
+ * @param symLink true
to set it to be marked as symbolic link,
+ * false
to unset
+ * @see #isSymbolicLink()
+ * @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.
+ */
+ public String toString() {
+ return "ResourceAttributes(" + attributes + ')'; //$NON-NLS-1$
+ }
+}
\ No newline at end of file
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,380 @@
+/*******************************************************************************
+ * 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;
+
+import org.eclipse.core.internal.resources.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.IJobManager;
+import org.eclipse.core.runtime.jobs.Job;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * 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$
+
+ /**
+ * 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.
+ *
null
is there is none.
+ */
+ private static Workspace workspace = null;
+
+ /**
+ * 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: + *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)
+ */
+ public void stop(BundleContext context) throws Exception {
+ super.stop(context);
+ if (workspace == null)
+ return;
+ // 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;
+ }
+
+ /**
+ * This implementation of the corresponding {@link BundleActivator} method
+ * opens the workspace.
+ * @see BundleActivator#start(BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ if (!new LocalMetaArea().hasSavedWorkspace()) {
+ constructWorkspace();
+ }
+ Workspace.DEBUG = ResourcesPlugin.getPlugin().isDebugging();
+ // Remember workspace before opening, to
+ // make it easier to debug cases where open() is failing.
+ workspace = new Workspace();
+ PlatformURLResourceConnection.startup(workspace.getRoot().getLocation());
+ IStatus result = workspace.open(null);
+ if (!result.isOK())
+ getLog().log(result);
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceJob.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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 - 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. + * @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
.
+ *
null
if progress
+ * reporting and cancelation are not desired
+ * @return the result of running the operation
+ * @exception CoreException if this operation fails.
+ */
+ public abstract IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException;
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceLock.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/WorkspaceLock.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * 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.resources;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * A lock used to control write access to the resources in a workspace.
+ * Clients may subclass.
+ *
+ * @see IWorkspace#setWorkspaceLock(WorkspaceLock)
+ * @deprecated it is no longer possible to override the workspace lock behavior.
+ * This functionality is now provided in the platform API by implementing the
+ * org.eclipse.core.runtime.jobs.ILockListener interface.
+ */
+public class WorkspaceLock {
+
+ /**
+ * Returns a new workspace lock.
+ */
+ public WorkspaceLock(IWorkspace workspace) throws CoreException {
+ //thwart compiler warning
+ if (false)
+ throw new CoreException(Status.OK_STATUS);
+ }
+
+ /**
+ * Attempts to acquire this lock. Callers will block indefinitely until this lock comes
+ * available to them.
+ * + * Clients may extend this method but should not otherwise call it. + *
+ * @see #release() + */ + public boolean acquire() throws InterruptedException { + //thwart compiler warning + if (false) + throw new InterruptedException(); + return false; + } + + /** + * Returns the thread that currently owns the workspace lock. + */ + protected Thread getCurrentOperationThread() { + //deprecated API + return null; + } + + /** + * Releases this lock allowing others to acquire it. + *+ * Clients may extend this method but should not otherwise call it. + *
+ * @see #acquire() + */ + public void release() { + //deprecated API + } + + /** + * Returns whether the workspace tree is locked + * for resource changes. + * + * @returntrue
if the tree is locked, otherwise
+ * false
+ */
+ protected boolean isTreeLocked() {
+ //deprecated API
+ return true;
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/CompositeResourceMapping.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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 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)
+ */
+ 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()
+ */
+ public Object getModelObject() {
+ return modelObject;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.mapping.ResourceMapping#getModelProviderId()
+ */
+ public String getModelProviderId() {
+ return providerId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.mapping.ResourceMapping#getProjects()
+ */
+ 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 = (IProject[]) 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)
+ */
+ 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 (ResourceTraversal[]) result.toArray(new ResourceTraversal[result.size()]);
+ } finally {
+ monitor.done();
+ }
+ }
+
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IModelProviderDescriptor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.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.
+ *
+ * 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"
.
+ *
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,85 @@ +/******************************************************************************* + * 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.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. + *
+ * This interface is not intended to be implemented by clients. + *
+ * + * @see ResourceChangeValidator + * @see ModelProvider + * @since 3.2 + */ +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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelProvider.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,266 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 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 ofResourceMapping
+ * objects that are part of the same model.
+ *
+ * TODO: include xml snippet
+ *
+ * + * 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 + * ornull
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)
+ */
+ 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"
.
+ *
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, ornull
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 (ResourceMapping[]) 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 extract from each traversal.
+ * + * Subclasses may override this method. + *
+ * + * @param traversals the traversals + * @param context a resource mapping context + * @param monitor a progress monitor, ornull
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 (ResourceMapping[]) 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, ornull
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 (ResourceTraversal[]) traversals.toArray(new ResourceTraversal[traversals.size()]);
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ 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
+ */
+ 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.
+ *
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ModelStatus.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/RemoteResourceMappingContext.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,320 @@ +/******************************************************************************* + * Copyright (c) 2005, 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.resources.*; +import org.eclipse.core.runtime.*; + +/** + * 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, ornull
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:
+ * 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.
+ *
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:
+ * + * 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, ornull
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:
+ * 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, ornull
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:
+ * 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.
+ *
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:
+ * 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.
+ *
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:
+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if the refresh fails. Reasons include:
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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, ornull
if progress
+ * reporting is not desired
+ * @exception CoreException if this method fails. Reasons include:
+ *
+ * This method always returns false
when the given resource
+ * mapping's model provider id does not match that the of the receiver.
+ *
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.
+ */
+ 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 (IMarker[]) 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 benull
, 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.
+ */
+ public int hashCode() {
+ return getModelObject().hashCode();
+ }
+}
diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceMappingContext.java Mon Jun 01 10:16:20 2009 -0500
@@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/ResourceTraversal.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,181 @@ +/******************************************************************************* + * Copyright (c) 2004, 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 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 theIResource#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:
+ * 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 (IMarker[]) 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshMonitor.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * 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.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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/IRefreshResult.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 - 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.
+ */
+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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/refresh/RefreshProvider.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * 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.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)
+ */
+ 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 + */ + public void resetMonitors(IResource resource) { + super.resetMonitors(resource); + } +} diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidationContext.java Mon Jun 01 10:16:20 2009 -0500 @@ -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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/FileModificationValidator.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * 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.*;
+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 + */ +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 isnull
, 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.
+ */
+ 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)
+ */
+ 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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IMoveDeleteHook.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,383 @@
+/*******************************************************************************
+ * 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.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.
+ *
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/IResourceTree.java Mon Jun 01 10:16:20 2009 -0500
@@ -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.
+ *
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.
+ *
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.
+ *
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.
+ *
+ * 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 + * @returntrue
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
.
+ *
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
.
+ *
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
.
+ *
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
.
+ *
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
.
+ *
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
.
+ *
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 -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/ResourceRuleFactory.java Mon Jun 01 10:16:20 2009 -0500
@@ -0,0 +1,233 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 - Initial API and implementation
+ *******************************************************************************/
+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()
+ */
+ 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 + */ + public ISchedulingRule charsetRule(IResource resource) { + if (resource.getType() == IResource.ROOT) + return null; + return resource.getProject(); + } + + /** + * Default implementation ofIResourceRuleFactory#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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ public final ISchedulingRule markerRule(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
+ */
+ 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)
+ */
+ 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)
+ */
+ 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[]) + */ + 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 (ISchedulingRule) rules.iterator().next(); + ISchedulingRule[] ruleArray = (ISchedulingRule[]) rules.toArray(new ISchedulingRule[rules.size()]); + return new MultiRule(ruleArray); + } +} diff -r b95605ba8677 -r 063eb66097dc platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/org.eclipse.core.resources/src/org/eclipse/core/resources/team/TeamHook.java Mon Jun 01 10:16:20 2009 -0500 @@ -0,0 +1,230 @@ +/******************************************************************************* + * 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.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.
+ *
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
+ */
+ 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 codeIStatus.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 codeIStatus.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 codeIStatus.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 codeIStatus.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;
+ }
+}