### Eclipse Workspace Patch 1.0 #P org.eclipse.core.jobs Index: src/org/eclipse/core/internal/jobs/InternalJob.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/InternalJob.java,v retrieving revision 1.14 diff -u -r1.14 InternalJob.java --- src/org/eclipse/core/internal/jobs/InternalJob.java 6 Feb 2010 21:05:24 -0000 1.14 +++ src/org/eclipse/core/internal/jobs/InternalJob.java 6 Apr 2010 15:23:49 -0000 @@ -11,7 +11,6 @@ *******************************************************************************/ package org.eclipse.core.internal.jobs; -import java.util.Map; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.jobs.*; @@ -94,7 +93,7 @@ * Arbitrary properties (key,value) pairs, attached * to a job instance by a third party. */ - private ObjectMap properties; + private ObjectMap properties = new ObjectMap(); /** * Volatile because it is usually set via a Worker thread and is read via a @@ -241,11 +240,7 @@ * @see Job#getProperty */ protected Object getProperty(QualifiedName key) { - // thread safety: (Concurrency001 - copy on write) - Map temp = properties; - if (temp == null) - return null; - return temp.get(key); + return properties.get(key); } /* (non-Javadoc) @@ -506,25 +501,15 @@ * @see Job#setProperty(QualifiedName,Object) */ protected void setProperty(QualifiedName key, Object value) { - // thread safety: (Concurrency001 - copy on write) - if (value == null) { - if (properties == null) - return; - ObjectMap temp = (ObjectMap) properties.clone(); - temp.remove(key); - if (temp.isEmpty()) - properties = null; - else - properties = temp; - } else { - ObjectMap temp = properties; - if (temp == null) - temp = new ObjectMap(5); - else - temp = (ObjectMap) properties.clone(); - temp.put(key, value); - properties = temp; - } + properties.put(key, value); + } + + /* + * (non-Javadoc) + * @see Job#setPropertyIfAbsent(QualifiedName,Object) + */ + protected Object setPropertyIfAbsent(QualifiedName key, Object value) { + return properties.putIfAbsent(key, value); } /** Index: src/org/eclipse/core/internal/jobs/ObjectMap.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ObjectMap.java,v retrieving revision 1.5 diff -u -r1.5 ObjectMap.java --- src/org/eclipse/core/internal/jobs/ObjectMap.java 22 Oct 2008 18:36:31 -0000 1.5 +++ src/org/eclipse/core/internal/jobs/ObjectMap.java 6 Apr 2010 15:23:49 -0000 @@ -11,6 +11,7 @@ package org.eclipse.core.internal.jobs; import java.util.*; +import java.util.concurrent.ConcurrentMap; /** * A specialized map implementation that is optimized for a small set of object @@ -18,14 +19,17 @@ * * Implemented as a single array that alternates keys and values. * - * Note: This class is copied from org.eclipse.core.resources + * Note: This class is copied from org.eclipse.core.resources and modified to have copy-on-write semantics */ public class ObjectMap implements Map { - // 8 attribute keys, 8 attribute values - protected static final int DEFAULT_SIZE = 16; - protected static final int GROW_SIZE = 10; - protected int count = 0; - protected Object[] elements = null; + + private static final Object[] emptyArray = new Object[0]; + + protected volatile Object[] elements = null; + + public ObjectMap() { + elements = emptyArray; + } /** * Creates a new object map. @@ -34,8 +38,7 @@ * 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)]; + this(); } /** @@ -54,8 +57,7 @@ * @see Map#clear() */ public void clear() { - elements = null; - count = 0; + elements = emptyArray; } /** @@ -69,10 +71,11 @@ * @see Map#containsKey(java.lang.Object) */ public boolean containsKey(Object key) { - if (elements == null || count == 0) + Object[] e = elements; + if (e.length == 0) return false; - for (int i = 0; i < elements.length; i = i + 2) - if (elements[i] != null && elements[i].equals(key)) + for (int i = 0; i < e.length; i = i + 2) + if (e[i] != null && e[i].equals(key)) return true; return false; } @@ -81,10 +84,11 @@ * @see Map#containsValue(java.lang.Object) */ public boolean containsValue(Object value) { - if (elements == null || count == 0) + Object[] e = elements; + if (e.length == 0) return false; - for (int i = 1; i < elements.length; i = i + 2) - if (elements[i] != null && elements[i].equals(value)) + for (int i = 1; i < e.length; i = i + 2) + if (e[i] != null && e[i].equals(value)) return true; return false; } @@ -97,70 +101,27 @@ * 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(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; + return toHashMap().entrySet(); } /** * @see Map#get(java.lang.Object) */ public Object get(Object key) { - if (elements == null || count == 0) + Object[] e = elements; + if (e.length == 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]; + for (int i = 0; i < e.length; i = i + 2) + if (e[i] != null && e[i].equals(key)) + return e[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; - } - - /** - * @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; + return elements.length == 0; } /** @@ -171,10 +132,11 @@ * 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]); + Object[] e = elements; + Set result = new HashSet(e.length); + for (int i = 0; i < e.length; i = i + 2) { + if (e[i] != null) { + result.add(e[i]); } } return result; @@ -183,24 +145,20 @@ /** * @see Map#put(java.lang.Object, java.lang.Object) */ - public Object put(Object key, Object value) { + public synchronized 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) { + if (elements.length == 0) { + elements = new Object[elements.length + 2]; 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)) { @@ -208,49 +166,96 @@ 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++; + + // it's a new key, add it to the end + Object[] newElements = new Object[elements.length + 2]; + System.arraycopy(elements, 0, newElements, 0, elements.length); + newElements[elements.length] = key; + newElements[elements.length + 1] = value; + + elements = newElements; return null; } /** + * {@link ConcurrentMap#putIfAbsent(Object, Object)} + */ + public Object putIfAbsent(Object key, Object value) { + if (key == null) + throw new NullPointerException(); + + Object v = get(key); + if (v != null) + return v; + synchronized (this) { + // need to re-check under sync for concurrent putIfAbsents + Object val = get(key); + if (val != null) + return val; + // it's a new key, add it to the end + Object[] newElements = new Object[elements.length + 2]; + System.arraycopy(elements, 0, newElements, 0, elements.length); + newElements[elements.length] = key; + newElements[elements.length + 1] = value; + + elements = newElements; + 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); + Object[] newElements = new Object[map.size() * 2]; + int i = 0; + for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) { + Map.Entry entry = (Map.Entry) itr.next(); + newElements[i++] = entry.getKey(); + newElements[i++] = entry.getValue(); + } + putAll(newElements); + } + + private synchronized void putAll(Object[] newEntries) { + Object[] total = new Object[elements.length + newEntries.length]; + int size = 0; + copy: for (int i = 0; i < elements.length; i = i + 2) { + for (int j = 0; j < newEntries.length; j = j + 2) { + if (elements[i].equals(newEntries[j])) { + total[size++] = elements[i]; + total[size++] = newEntries[j + 1]; + continue copy; + } + } + } + if (size == total.length) { + elements = total; + return; } + //trim + Object[] newElements = new Object[size]; + System.arraycopy(total, 0, newElements, 0, size); + elements = newElements; } /** * @see Map#remove(java.lang.Object) */ - public Object remove(Object key) { - if (elements == null || count == 0) + public synchronized Object remove(Object key) { + if (elements.length == 0) return null; + for (int i = 0; i < elements.length; i = i + 2) { - if (elements[i] != null && elements[i].equals(key)) { - elements[i] = null; + if (elements[i].equals(key)) { Object result = elements[i + 1]; - elements[i + 1] = null; - count--; + // copy into new array, excluding match + Object[] newElements = new Object[elements.length - 2]; + System.arraycopy(elements, 0, newElements, 0, i); + System.arraycopy(elements, i + 1, newElements, i, elements.length - 2); + elements = newElements; return result; } } @@ -261,17 +266,18 @@ * @see Map#size() */ public int size() { - return count; + return elements.length / 2; } /** * 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]); + Object[] e = elements; + HashMap result = new HashMap(e.length); + for (int i = 0; i < e.length; i = i + 2) { + if (e[i] != null) { + result.put(e[i], e[i + 1]); } } return result; @@ -285,10 +291,11 @@ * 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]); + Object[] e = elements; + Set result = new HashSet(e.length); + for (int i = 1; i < e.length; i = i + 2) { + if (e[i] != null) { + result.add(e[i]); } } return result; Index: src/org/eclipse/core/runtime/jobs/Job.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/Job.java,v retrieving revision 1.18 diff -u -r1.18 Job.java --- src/org/eclipse/core/runtime/jobs/Job.java 19 Feb 2010 20:39:59 -0000 1.18 +++ src/org/eclipse/core/runtime/jobs/Job.java 6 Apr 2010 15:23:49 -0000 @@ -533,6 +533,32 @@ } /** + * If the specified key is not already associated with a value, associate it with + * the given value. If the supplied value is null, the property + * is removed from this resource.

This is equivalent to + *

+	 *   if (job.getProperty(key)==null) {
+	 *       job.setProperty(key, value);
+	 *       return null;
+	 *   } else {
+	 *       return job.getProperty(key);
+	 *   }
except that the action is performed atomically. + *

+ * Properties are intended to be used as a caching mechanism + * by ISV plug-ins. They allow key-object associations to be stored with + * a job instance. These key-value associations are maintained in + * memory (at all times), and the information is never discarded automatically. + *

+ * The qualifier part of the property name must be the unique identifier + * of the declaring plug-in (e.g. "com.example.plugin"). + * @return The previous value for the key, or null if no key if there was no mapping. + * @since 3.5 + */ + public Object setPropertyIfAbsent(QualifiedName key, Object value) { + return super.setPropertyIfAbsent(key, value); + } + + /** * Sets the scheduling rule to be used when scheduling this job. This method * must be called before the job is scheduled. *