/*******************************************************************************
* 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;
}
}